The code goes in circles

Published:

By Friday, the pull request has changed every day and moved nowhere.

Monday's version changes the function. Tuesday's extracts its helper. Wednesday's folds the helper back in. A standard library operation becomes a custom implementation. Names move, branches trade places, and a test is rewritten around each new shape. The pull request remains open. Its purpose becomes harder to explain.

Activity continues while progress becomes difficult to find.

This is the pattern sometimes called bit twiddling: repeated, low-impact changes to a narrow area after the work has stopped producing useful learning. The engineer keeps turning the same pieces because no arrangement feels finished, yet each turn supplies less evidence about what the solution should become.

The pattern can look like care. The engineer is still at the keyboard, still committing, and still trying to improve the code. They may describe the work as refinement, optimization, cleanup, or one final edge case. A repository records motion and gives little indication that the person making it has lost a clear path to the outcome.

The essential signal is a combination: narrow focus, repeated revision, low impact, and fading conviction.

Any one of these can be healthy. A security-sensitive function may deserve prolonged attention. A difficult algorithm may take several attempts. A careful refactor may touch the same lines repeatedly while tests preserve a stable direction. Productive iteration has a learning curve. Each pass answers a question, eliminates an option, or moves the work closer to an agreed result.

Work going in circles has a different rhythm. The changes become interchangeable. One version provides little reason to prefer it over the previous version. Review descriptions become generic: “cleanup,” “reorganizing,” “minor optimization,” or “another pass.” Asked what remains, the engineer can describe details in the code more readily than the decision the code still needs to support.

The work has become detached from a stopping condition.

Software offers endless opportunities for adjustment. Almost every function can be renamed, generalized, specialized, shortened, decomposed, recombined, or made more clever. A clear outcome tells an engineer which of those possibilities matter. When that outcome becomes weak or distant, the available possibilities can absorb an extraordinary amount of attention.

Several causes can create this shape.

The engineer may lack important context. They understand the requested change but have an incomplete model of the domain around it. Each implementation collides with another rule, so they rearrange the local code without resolving the missing idea. The code feels unstable because the engineer is trying to infer a business decision from technical artifacts.

The problem may lack a decision owner. Product wants the behavior to feel simple, design has left an edge case open, and engineering has several defensible interpretations. Nobody resolves the choice. The engineer keeps polishing the implementation as though technical quality could compensate for an unanswered product question.

The feedback loop may be too slow. A test suite takes an hour. The relevant behavior appears only in a shared environment. Production data is unavailable. Each attempt costs enough time that the engineer makes several speculative changes before learning from the first one. By the time evidence arrives, the relationship between cause and result has become unclear.

Perfectionism can keep the loop turning. The engineer recognizes that the code works but cannot accept its current form. They imagine a cleaner abstraction, build it, then notice the abstraction's cost. They return to the concrete version with different names and begin again. Their standard of completion lives in a feeling of elegance that the task may never satisfy.

Fatigue creates a similar result. After enough time with the same problem, distinctions lose sharpness. The engineer reads the code they expect to see. Small edits provide the relief of action without demanding another difficult attempt to understand the whole. Concentration becomes persistence after persistence has stopped helping.

The assignment may also have lost meaning. A project changed direction while its remaining technical task stayed on the board. A promised integration no longer has a user. An optimization began with a performance concern nobody has measured. The engineer continues because the work was assigned, while every local decision feels arbitrary because the larger reason has weakened.

These causes produce similar traces and require different help. Repository data can reveal the location of the loop. A conversation has to reveal its mechanism.

Look for the same small area changing repeatedly across several days or pull-request revisions. Compare the amount of activity with the movement in behavior. Read the commit and review descriptions. Notice whether the author can state what was learned, what choice was made, and what evidence would make the work complete.

The review itself often changes character.

Early comments may engage with behavior and design. Later comments focus on names, formatting, and small rearrangements because the central question has become difficult to see. Reviewers approve a version and then receive another version solving the same problem differently. They may withdraw from the discussion, assuming the author wants to keep working until personally satisfied.

An apparently quiet review can therefore accompany intense private activity. The absence of disagreement does not mean the direction is clear. It may mean the rest of the team has stopped knowing what kind of feedback would move the work.

This pattern overlaps with high churn, but the distinction matters.

High churn describes code repeatedly rewritten as uncertainty moves through the work. Requirements change, feedback arrives, an architecture reveals a hidden constraint, or an early experiment teaches the team something. The movement can be expensive while still carrying information.

Code going in circles is a narrower failure of direction. The same area changes while the important uncertainty remains unnamed. Versions accumulate without materially changing what the team knows. Churn measures the rewriting; the circle appears when rewriting no longer reduces uncertainty.

A useful test is to ask what became clearer after the latest pass.

The engineer may have learned that a particular API cannot support the required transaction, that a data shape performs poorly at the expected volume, or that users interpret an interaction differently than the team assumed. Those are valuable results, even when code is discarded.

If the answer is mainly that the latest version feels tidier, ask what decision that tidiness serves. The answer may expose a real maintainability concern. It may also reveal that the work has continued beyond the point where another internal arrangement creates meaningful value.

Managers should approach the pattern with curiosity because stalled work already carries shame.

An engineer usually knows when a task has consumed too much time. They see colleagues finishing other work. Stand-up turns into a daily exercise in finding a new description for the same position. The open pull request becomes evidence they imagine everyone else is judging. That pressure encourages them to produce another change before asking for help, which makes the loop deeper.

A performance accusation adds pressure while leaving the missing direction untouched. A useful intervention reduces the size of the problem and restores evidence.

Begin with the outcome. What needs to be true when this work is finished? Who will observe the difference? Which risk justifies the change? Which parts are required for the first useful version? A task that cannot answer these questions needs clarification before it needs more implementation.

Then reconstruct the turns. Which version came closest? What caused the engineer to move away from it? Which concern keeps returning? Which assumption has never been tested? The history often contains one unresolved decision that every refactor has been circling.

Turn that decision into a question small enough to answer.

Measure the operation before optimizing it. Ask the domain expert about the rule the code is trying to infer. Put two interface options in front of the person who will use them. Write a characterization test around the behavior that must survive. Build a thin path through the real environment. Replace a broad standard such as “make this extensible” with the next known variation the design must accommodate.

Evidence gives the work a direction and a boundary.

Pairing can help when the engineer has spent too long inside their own model. A second person can notice an assumption that has become invisible, ask why an optimization exists, or identify the missing domain concept. The purpose is shared diagnosis rather than rescue. Taking the keyboard and finishing the task may clear the ticket while teaching the engineer that difficulty transfers ownership.

A short working session is often enough. Have the engineer explain the desired behavior, the versions already attempted, and the reason each one was abandoned. The listener should reflect the decision tree back. Frequently, the act of making the turns legible reveals where the work left the main path.

Sometimes the right move is to stop.

Stopping can mean merging the smallest adequate version, preserving an experiment and closing the task, or recording what was learned before removing the branch. Completion should follow an explicit standard. “This meets the measured requirement, passes the agreed cases, and leaves one documented follow-up” gives the engineer firmer ground than “this is probably good enough.”

Sometimes the engineer needs a temporary change of context.

A small, well-bounded task in another area can restore momentum and perspective. It offers a complete feedback loop: understand, change, verify, finish. Returning later with fresh attention may make the original problem easier to see. A permanent reassignment can also be appropriate when the work no longer matches the person's interests or the context they were given.

Use the change deliberately. Moving every difficult task away from someone prevents them from developing the skill to navigate ambiguity. Leaving someone indefinitely inside an exhausted loop turns endurance into a test with no learning objective. The manager and engineer should agree on why the context is changing and what would make a return useful.

Prevention begins before the first line changes.

Give uncertain work a question rather than a false promise of delivery. A spike should name what it intends to discover and when the team will evaluate the result. An optimization should begin with a measurement and a target. A refactor should identify the future change, present cost, or safety concern it improves. A feature with unresolved product behavior should expose that decision early.

Create intermediate points where direction can be checked while it remains cheap to change. A design note, draft pull request, test outline, or brief demonstration can reveal whether the engineer and the rest of the team are solving the same problem. These checkpoints work best as invitations to compare understanding, rather than gates where someone must defend unfinished work.

Teams also need language for diminishing returns.

An engineer should be able to say, “I have made three versions and learned little from the latest one.” A reviewer should be able to ask, “What new information would justify another pass?” A manager should be able to pause a task without presenting the pause as failure. This language turns stuckness into observable project state instead of private weakness.

Watch for organizational conditions that create repeated circles.

One team may receive vague optimization requests without production measurements. Another may have a product owner who postpones edge-case decisions. A third may reward elegant abstractions while rarely checking whether they reduce delivery cost. Another may give junior engineers isolated tasks in mature domains without access to the people who know why the code looks strange.

When several engineers get stuck in the same area, the area is speaking. Its boundaries may be unclear, its tests may provide weak feedback, or its rules may live in people's memories. Treating each case as an individual motivation problem wastes the strongest evidence the pattern provides.

The manager's aim is to restore useful movement.

Useful movement can be code, but it can also be a decision, a measurement, a smaller question, a documented constraint, or a clear stop. The important change is that each step alters what the team knows and makes the next step easier to choose.

Activity shows that someone is working. Progress shows that the work is converging.

When the code keeps returning to the same shape, look beyond the edits. Find the outcome that has gone blurry, the evidence that is missing, or the decision nobody has made. Give the engineer enough distance and shared context to see the whole puzzle again.

The loop breaks when another turn of the piece becomes an answerable question.