Why Engineering Teams Stop Shipping · Part 1

The Complexity Tipping Point

Delivery rarely collapses overnight. Six months ago the team shipped in days. Now a small feature needs six meetings. Here is what a complexity tipping point looks like, and the first two weeks of work.

Delivery rarely collapses overnight.

Six months ago, the team shipped in days. Now a "small" feature needs six meetings and an estimate nobody believes. People are working just as hard, but getting a change into production takes more effort than it used to.

Sometimes the problem is shifting priorities or too much work in progress. But it's also worth looking at the system itself. How much does the team have to understand, coordinate, and check before it can change anything?

That's what I mean by the complexity tipping point: the stage where managing the connections between parts of the system starts to crowd out the work of improving it.

When a codebase is small, much of that coordination happens without anyone thinking about it. The engineers know how things fit together. They can ask a question across a desk or make a change without involving the rest of the team.

Over time, integrations, exceptions, and workarounds create dependencies between things that used to be separate. Adding one more component can mean adding several more relationships to keep track of.

The details are familiar. A database table written by three services. A shared utilities module that half the codebase imports and nobody owns. Environment-specific behavior buried in code. Feature flags that were never removed. A third-party integration that turns local setup into a recurring project. A cron job nobody remembers writing that the system still depends on.

Many of these started as reasonable shortcuts. They helped get something out the door. The cost was paid by the next change instead of that one.

That is the useful part of the technical debt metaphor: the extra work shows up when you try to do something else. Martin Fowler describes that additional effort as the interest on the debt. It also helps explain why the parts worth improving first are often the ones you keep having to change. Martin Fowler, Technical Debt.

The teams I see run into this are often six to fifteen engineers who have spent eighteen months moving quickly and building something customers want. Architecture keeps getting pushed behind the next release, the next round, or the senior hire who will finally have time to look at it.

It's understandable. Everyone can see the feature that needs to ship. The growing effort behind each feature is harder to see, especially while the team is still finding ways to get things done.

DORA identifies the ability to change, test, and deploy software independently as a predictor of continuous delivery. When those activities require constant coordination, delivery gets harder. For a small team, the useful question is how much you can change without pulling everyone else in. DORA, Loosely coupled teams.

By the time someone asks why the team has slowed down, a few patterns may already be familiar:

  • A two-day ticket takes two weeks because the work reaches further into the system than anyone expected.
  • Reviews wait for the one person who understands payments, and more changes seem to need that person's attention.
  • Deployments feel risky, so the team deploys less often. More changes accumulate between releases, making the next one harder to review and troubleshoot.
  • "We should rewrite this" comes up regularly. The team can describe the pain of the current system much more clearly than the work of replacing it.
  • New hires need so much context from existing engineers that adding people initially slows things down further.

You can get a useful first picture in a week. Start with recent work and a few conversations:

  1. Trace the last twenty merged pull requests. For each, note the services, schemas, and repositories involved, along with dependencies discovered after work began. Look for apparently small changes that repeatedly spread across the system. There is no magic number of components that makes a change too complex; the useful question is why those connections were necessary. Ask which areas engineers most want to replace and whether those are the same places slowing down recent work.

  2. Ask two engineers to sketch how the system runs in production. Compare their drawings, particularly the dependencies and ownership boundaries. Different perspectives are normal. Disagreement about where data goes or which service does what is worth resolving before the next change relies on that assumption.

  3. Try a fresh setup. How long does it take to make a change and see it work? Note the undocumented steps, borrowed environment files, and reliance on shared staging systems. Then look at the tests: do failures point to real problems, or has the team learned to ignore them? Both tell you how dependable the feedback is before a release.

  4. Look at deployments over time. Are they becoming less frequent and larger? How often do they need a rollback or an urgent fix? Use these patterns to identify where confidence has fallen and ask what changed.

  5. Follow the waiting time. Which reviews, meetings, and sign-offs does a change pass through? Ask what each step protects against. Some need human judgment. Others may exist because a test, a reliable environment, or visibility into production is missing.

I would start there before committing to a rewrite. Replacing a system means understanding existing behavior, moving data and users, and keeping the business running through the transition. That work needs room alongside the features the team already owes. A new platform, microservices, or more engineers can help in the right circumstances, but each can also add work before it removes any.

For the first two weeks, set a narrower goal: make one valuable change easier to ship, and learn what made it difficult.

Choose something the business actually needs, then reduce it to a useful slice the team can test and release. Keep the customer benefit intact. A smaller ticket doesn't help much if it still has to wait for five other tickets before anyone can use it. DORA makes this distinction in its guidance on small batches: dividing work helps only if you avoid bundling it back together for testing and release. DORA, Working in small batches.

As you work through that change, look for a boundary you can make clearer. If several parts of the application reach directly into the same troublesome module, a shared interface may let you move those callers behind one entry point. With tests around the behavior, you can begin changing the implementation in smaller steps. This is the approach Fowler describes in Branch by Abstraction, which allows replacement work to proceed while the system continues to ship. Martin Fowler, Branch by Abstraction.

Also look for one recurring manual check you can make dependable. Part of a deployment checklist might become an automated smoke test. Documenting billing behavior and adding tests around it might help another engineer review changes that currently wait for one person. Retire the old step once the replacement covers the risk it was handling.

Use the first week to understand the delays and choose the change. Use the second to ship it and address the most immediate obstacle. The two weeks give the effort a boundary; they aren't a promise that the architecture will be fixed by Friday.

What you want at the end is a finished piece of work and a specific account of what held it up. That gives you a better basis for deciding what to improve next, including whether any part really does need replacing.

As more changes become manageable, the team can spend less time coordinating around the system and more time improving the product.

The next piece, Busy Is Not Shipping, looks at another response to this problem: staying busy with work around the difficult parts while the changes customers need keep waiting.

Related: Engineering team not shipping?