High churn

Published:

Some code changes many times before it settles.

A file is rewritten after review. A feature grows a second version inside the same branch. Tests are added, removed, and added again. The same lines move through several shapes before the pull request merges. From a distance, the work can look inefficient. Up close, it may be the team learning what the problem actually is.

Churn is part of software work. People explore. Designs fail. Requirements become clearer only after someone tries to implement them. A prototype gets thrown away because it did its job. A test reveals that the first solution was too narrow. An abstraction looks right on Monday and costly by Thursday. This is normal. A team with no churn may be doing simple work, hiding rework elsewhere, or merging code before enough thinking has happened.

The signal appears when churn becomes unusually high for the person, team, project, or stage of work.

The important word is unusually. A high number in isolation says little. A platform migration may churn heavily because every boundary is being touched. A proof of concept may churn heavily because the point is discovery. A new product surface may churn heavily because design and implementation are learning together. The same amount of churn in a routine feature near the end of a sprint means something different.

Churn needs a baseline before it can become a diagnosis. How does this engineer usually work? How does this team usually work? How much rework is typical in this codebase? Which kinds of tickets naturally produce iteration? Which areas of the system are expensive to change cleanly? Without that context, the metric becomes a personality test, and personality tests are a poor way to manage software.

High churn usually means the work is meeting uncertainty somewhere.

Sometimes the uncertainty belongs to the problem. The engineer thought the task was understood, opened the code, and found that the model did not support the request. The first implementation solved the visible case but broke a hidden one. A test exposed a state the ticket never mentioned. A dependency behaved differently in production than in the local environment. The code keeps changing because the problem keeps revealing itself.

Sometimes the uncertainty belongs to the solution. The engineer can describe the goal but has not found the right shape yet. They try one boundary, then another. They move behavior between layers. They simplify a type, then add structure back. This can be healthy design work, especially early. It becomes costly when the work keeps happening inside a delivery commitment that assumed the design was already known.

Sometimes the uncertainty belongs to the organization.

A stakeholder changes the requirement after seeing a partial result. Product adds an edge case halfway through implementation. A deadline remains fixed while the requested behavior expands. A manager asks for confidence before discovery has happened, and the team pays for that confidence later through rewrites. The churn appears in code, but the cause sits outside the editor.

This is one reason high churn is easy to misread. The person committing the rework becomes the visible owner of the waste. They may be the person absorbing ambiguity created earlier by planning, product, architecture, or review. Treating the churn as an individual failure can hide the system that produced it.

The timing matters.

Early churn often means exploration. A developer tries a path, learns from it, and redirects before too many people depend on the result. That kind of churn can be productive. It reduces risk by making wrong ideas cheaper. It can even be a sign of good judgment: the engineer is willing to throw away code before the team has to live with it.

Late churn carries a different weight. When large rewrites happen near a deadline, the team loses options. Reviewers have less time to understand the change. Product has less time to adjust scope. The author has less energy for another direction. Testing becomes compressed. The conversation shifts from "what should this be?" to "can we still ship?"

That is where churn starts shaping the team.

If late rework happens once, the cause may be a hard problem. If it repeats, the team should look at how work is prepared before implementation begins. Tickets may be too vague. Discovery may be treated as implementation. Stakeholders may be adding requirements through side channels. Engineers may be accepting work before they know where the risk is. Review may be arriving after the important decisions have already hardened.

Perfectionism can also create churn.

An engineer keeps returning to code that already works because it does not feel right yet. They rename, reshape, extract, inline, and rebuild. Each pass improves something local. The total effect may still be delay without corresponding product value. This pattern is hard because the instinct often comes from care. The engineer wants the system to be clean. They have seen poor choices become maintenance burdens. They may be protecting the team from future pain.

The question is whether the extra change is buying useful options.

Good refinement reduces future cost, clarifies intent, or removes risk. Perfectionist churn often improves the feeling of the code more than the behavior of the system. The distinction is contextual. A core payment boundary deserves more care than an internal admin filter. A library used by ten teams deserves more stability than a one-off migration script. A manager or tech lead should help name the standard of "good enough" for the work in front of the team.

Struggle looks different from perfectionism.

The engineer thought they had solved the problem, then discovered they had solved the wrong one. They keep rewriting because each version exposes another misunderstanding. Their pull request may show large deletions, repeated rewrites of the same area, or review threads where the same concept has to be explained multiple times. This does not automatically mean the engineer lacks ability. The domain may be unfamiliar. The ticket may be too broad. The code may have hidden coupling. The team may have left them alone with a problem that needed shared context.

When churn signals struggle, the useful response is support before judgment.

Ask where the work changed direction. Ask what was learned between versions. Ask which assumption failed. Ask which part of the problem still feels unstable. The answers usually point toward the next intervention: a design conversation, a pairing session, a smaller slice, a domain walkthrough, or a decision from product about what matters most.

The worst response is to make the engineer defend every changed line as if rework itself were suspicious. That teaches people to hide exploration, squash the history into a cleaner story, or wait longer before sharing work. The team loses the signal and keeps the cost.

Use churn as a conversation starter.

"I noticed this ticket changed shape a lot near the end" is more useful than "your churn is high." The first sentence names a pattern in the work. The second sentence makes the person the problem. A manager should be curious about cause, timing, and repeatability. Did the work change because the requirement changed? Because review arrived late? Because the engineer discovered a better model? Because the codebase made the simple path hard? Because the team never agreed what good enough meant?

Once the cause is clearer, the response can be specific.

If stakeholders are driving the churn, make the rework visible outside engineering. Show how late changes moved the work. Separate the original scope from the additions. Decide whether to cut scope, move additions into follow-up work, or reset the timeline with the real shape of the request in view.

If the churn comes from unclear technical direction, move feedback earlier. Ask for a design sketch before full implementation. Open a draft pull request around the uncertain boundary. Pair on the first slice. Bring in someone who knows the domain before the engineer has spent a week building around a wrong assumption.

If the churn comes from perfectionism, clarify standards. Name the risk the extra polish is meant to reduce. Decide whether that risk matters for this work. Give the engineer a way to protect quality without turning every ticket into a private refactor. Sometimes that means a follow-up cleanup ticket. Sometimes it means accepting a simpler implementation. Sometimes it means agreeing that this area deserves the extra care.

If the churn comes from a codebase that makes small changes difficult, treat that as architecture feedback. Rework may be exposing coupling, unclear boundaries, weak tests, or slow local feedback. A team can punish the person doing the rework, or it can notice that the system makes correctness expensive.

Healthy teams reduce harmful churn by making uncertainty visible earlier. They name discovery as discovery. They split tickets when the unknown part is larger than expected. They use spikes without pretending the spike is the finished feature. They ask for review when direction is still movable. They let engineers say, "I know the goal, but I do not know the shape yet," without treating that as poor performance. They also protect useful churn.

Exploration has value. Throwing away a bad solution early is cheaper than maintaining it for years. Rewriting a boundary after learning more can be the responsible choice. The goal is to understand which rework is buying learning, which rework is paying for late decisions, and which rework is hiding a support problem.

Look for the pattern over time.

One churning ticket is an incident. A recurring pocket of churn is a map. It may point to a product area where requirements arrive late. It may point to a service with weak boundaries. It may point to an engineer who needs help calibrating quality. It may point to a team that treats planning as commitment before discovery has earned that commitment.

The code is where the movement becomes visible. The cause may be anywhere in the system.

High churn asks a manager to slow down enough to read the movement. Which parts changed? When did they change? Who learned what, and when? What would have made the learning cheaper? The answers reveal whether the team is exploring well, struggling silently, polishing past the point of value, or absorbing instability from outside the code.

Work keeps changing shape when the team is still finding the truth.

The management task is to make that search visible early enough that the team can learn without turning every lesson into a late rewrite.