Two side-by-side diagrams — left shows a single god class with many contributor lines converging and a long lead-time arrow, right shows the same feature split across small bounded modules with parallel contributor lines and a short lead-time arrow
Architecture, Engineering Practices, Continuous Delivery

God Classes, Circular Dependencies, and the Architecture That Kills Your Lead Time

By Vishvjitsinh Vanar13 min read

Slow lead time is rarely a process problem. It is a structural problem dressed up as one. God classes force every team to contend for the same file, serialising work that should be parallel. Circular dependencies make a one-line change ripple across modules, turning a half-day task into a week of coordination. Until the architecture is fixed, no amount of sprint discipline will move the number — because the bottleneck is not in the schedule, it is in the dependency graph.

The retrospective concluded that lead time was slow because of code reviews. The reviews were taking three days on average. The team agreed to a two-hour SLA. They added a Slack bot to escalate stale reviews. They scheduled a "review hour" every afternoon.

Six weeks later, lead time had not moved. The review SLA was being hit. The reviewers were responsive. The bot was working. The number that mattered — commit to production — was still ten days.

What the retro had not asked is why every PR needed a senior engineer's review. The answer was that every meaningful change touched the same three files. Those three files owned checkout, payments, inventory, and user state. The only person who reliably understood the wiring was the senior engineer. The review queue was not slow because reviewers were slow. The review queue was slow because the codebase had a structural single point of failure, and the queue had simply moved to where the failure lived.

This is the most expensive mistake in engineering leadership: treating lead time as a process problem when it is a structural problem.

What Slow Lead Time Actually Is

DORA defines lead time as the elapsed time between code committed and code running in production. Elite teams measure it in hours. The industry median measures it in weeks. The difference is rarely engineering velocity — engineers in slow teams are not typing more slowly than engineers in fast teams. The difference is waiting.

Lead time is dominated by waiting:

  • Waiting for a reviewer who has the context to approve the change.
  • Waiting for a merge conflict to resolve because someone else changed the same file.
  • Waiting for the architect to clarify which layer the new code belongs in.
  • Waiting for a test suite to run, fail on an unrelated module, and re-run.
  • Waiting for a release window because changes have to ship together.

Every waiting state is a coordination event — a moment where the work cannot proceed independently because something else has to align first. The faster a team's lead time, the fewer coordination events per change. The slower a team's lead time, the more changes require half the org to weigh in.

Code structure decides how many coordination events a change requires. A change that touches one file owned by one team needs one reviewer. A change that touches a god class needs everyone who depends on that class to be consulted. A change inside a dependency cycle needs every module in the cycle to be retested together.

The structural shape of the codebase is the upstream cause of cycle-time variance. Process changes treat the symptom. The disease lives in the dependency graph.

How God Classes Serialise Delivery

A god class is a file that owns multiple unrelated responsibilities — usually the largest file in the codebase, usually the one with the most contributors, usually the one nobody wants to touch but everyone has to.

The mechanics of how a god class kills lead time:

Every team contends for the same file. Three teams shipping features all need to add code to OrderService.ts. They cannot work in parallel — every PR that lands requires the next one to rebase. The team that shipped first has the cheapest path. The team that ships fourth has rebased three times, resolved three sets of conflicts, and re-run the test suite three times. The work is the same; the coordination cost is exponential.

One reviewer becomes the bottleneck. Because the file owns multiple domains, no single engineer except the original author understands the full context. Every PR that touches the file routes to them. Their review queue grows. The team's lead time becomes a function of one person's calendar.

The blast radius of every change is unbounded. A change to one method in the god class can affect every consumer of every other method, because everything is wired through the same internal state. Reviewers cannot reason about local correctness — they have to think about the entire file. Reviews get longer. Mistakes ship anyway because nobody has the full picture.

Refactoring becomes impossible. The file is too big to split safely under deadline pressure. Every quarter, someone proposes "let's clean up OrderService." Every quarter, the work is deprioritised because three teams have features in flight that touch the file. The file grows. The lead time grows with it.

In a codebase we audited last year, a single file was responsible for 41% of PRs over six months. The median time-to-merge for PRs touching that file was 4.1 days. The median for PRs that did not touch it was 11 hours. The structural choke point was a single 4,800-line class.

How Circular Dependencies Compound the Problem

A circular dependency is when module A depends on module B, which depends back on A — directly or through a chain. They are usually invisible until they hurt.

The mechanics of how cycles kill lead time:

Every change ripples. A modification to module A may break tests in module B (because B depends on A's behaviour) which requires updates in C (because C calls into B with assumptions about A's old behaviour) which depends back on A. What should have been a one-line fix becomes a five-file PR that touches three teams' code.

The boundary of the change is unbounded. When dependencies flow in both directions, you cannot reason about any module in isolation. Every change to A has to consider how B and C will react. Engineers learn to be conservative — to ship the smallest possible change to avoid awakening the cycle. The smallest change is rarely the right change, and the architecture compounds against itself over time.

Build times explode. Modules in a cycle cannot be built independently. A change to any one module forces a rebuild of all modules in the cycle. CI takes longer. Test feedback gets slower. Engineers wait more. Lead time grows.

Onboarding takes weeks instead of days. A new engineer cannot learn one module at a time because no module makes sense on its own. They have to absorb the cycle. The senior who understands the wiring becomes the bottleneck for every onboarding conversation — see also the post on senior engineers as a bottleneck, which is downstream of this same structural problem.

Two diagrams side by side. Left: a god class file at the centre with many contributor arrows converging onto it and a long red lead-time bar. Right: the same feature decomposed into small bounded modules with parallel contributor arrows reaching production directly, and a short green lead-time bar.
Figure 1: The lead-time difference. A god class converts parallel work into serial coordination. Bounded modules let independent changes ship in parallel.

Figure 1: The lead-time difference. A god class converts parallel work into serial coordination. Bounded modules let independent changes ship in parallel.

What to Measure First

You cannot fix the architecture until you know which parts of it are choking you. Three measurements expose the structural cause of slow lead time. None of them require a new tool — most teams' Git history and dependency graph already contain the answer.

1. File change concentration. Run git log --format=format: --name-only --since='90 days ago' | sort | uniq -c | sort -rn | head -20 on your repository. The top 20 files are where work is concentrating. If any single file accounts for more than 15% of changes, or if the top three files together account for more than 30%, you have god-class concentration. Open each of those files. If they own multiple domains — checkout AND payments AND inventory — they are your structural bottlenecks.

2. Dependency cycle detection. Tools like dependency-cruiser (TypeScript), madge (JavaScript), ArchUnit (Java), pydeps (Python) will list circular dependencies in your codebase. The number you want to see is zero. Any cycle is a permanent coordination cost. Cycles between layers (e.g. domain depending on infrastructure) are the worst because they violate the Clean Architecture rule that dependencies flow inward.

3. Contributor concentration per file. For each high-change file, count distinct contributors in the last 30 days. If more than five engineers have touched the same file in a month, that file is the serialisation point — every one of them was either blocking another or being blocked.

Three numbers, one afternoon. They expose the structural cause of cycle-time variance with more precision than any retrospective.

The Fix Is Incremental, Not a Rewrite

The instinct is to rewrite the god class. The instinct is wrong — rewrites of structurally tangled code are the projects that fail, slip, and get abandoned (the rewrite trap).

The fix is incremental, using the strangler pattern Michael Feathers describes in Working Effectively with Legacy Code:

Identify one cohesive responsibility inside the god class. Pick the smallest, most independently testable domain. In an OrderService god class, that might be "discount calculation" — it has a clear input (cart + applicable codes) and a clear output (final amount), and it does not need to know about payment or inventory.

Extract that responsibility into a new module with clean boundaries. New file, new module, narrow public interface. Move the methods. Move the tests. The god class now delegates to the new module for that responsibility.

Update callers one at a time. Every PR that used to touch OrderService for discount logic now touches DiscountCalculator instead. Other teams can keep working on the rest of OrderService in parallel. The serialisation point is shrinking.

Repeat with the next cohesive responsibility. Each extraction makes the next one easier because the god class is smaller and the boundaries are clearer. A six-month rewrite becomes a six-week sequence of safe, shippable refactorings.

For circular dependencies, the fix is mechanical: introduce an interface or event in the layer that is being depended upon backward, and invert the dependency. Module A no longer calls into module B directly — B implements an interface that A defines, and A passes the implementation in. The cycle becomes a one-way arrow. The tooling will tell you the cycle is gone the next time you run it.

Prevent Recurrence with Fitness Functions

Refactoring without prevention is rebuilding the same problem in five years. The rules that should hold once the architecture is clean need to be encoded as architecture fitness functions — automated checks that run in CI and fail the build when they are violated.

The minimal set:

  • No file exceeds X lines. A hard cap (e.g. 500 lines) on file size. New code goes in new files. The god class cannot grow back.
  • No circular dependencies between modules. dependency-cruiser, ArchUnit, or equivalent runs on every PR. Cycles fail the build.
  • Dependency direction enforced between layers. Domain cannot depend on infrastructure. Application cannot be imported from outside the application layer. Layer rules are mechanical, not aspirational.
  • No file is changed by more than N teams in M days. Optional, but a useful canary. When a file starts attracting too many contributors, the build flags it before it becomes the next god class.

Two-column flow diagram. Left column shows measurement: file concentration, dependency cycles, contributor concentration — each with example numbers. Middle column shows the incremental refactor: extract one responsibility, update callers, repeat. Right column shows prevention: fitness functions in CI enforcing file size, no cycles, dependency direction.
Figure 2: Measure structural drag, refactor incrementally, prevent recurrence with fitness functions. Each step is small. Each step is shippable. The sequence converts structural debt into lead time.

Figure 2: Measure structural drag, refactor incrementally, prevent recurrence with fitness functions. Each step is small. Each step is shippable. The sequence converts structural debt into lead time.

This is the move that compounds. Every fitness function added is a class of architectural drift that the team will never have to retro about again. The cost of adding the function is one afternoon. The cost of debating "is this file too big?" on every PR for the next five years is calendar weeks of senior engineering time.

What to Do This Week

You do not need a quarter to start. Four moves, sequenced over a week:

  1. Run the three measurements today. File change concentration, dependency cycles, contributor concentration. Twenty minutes total. The output is your structural debt map.

  2. Pick the top-ranked god class. The file with the highest change concentration. Open it. Identify the one most cohesive responsibility you can extract this sprint.

  3. Extract it. One responsibility, one new module, narrow interface, updated callers. Ship the PR. Measure the next week's lead time on that area — it will be shorter, sometimes dramatically so.

  4. Add one fitness function. The simplest one — a max-file-size check on the directory you just refactored. The build fails when the next engineer is tempted to add another 800 lines to the wrong file.

The first iteration is the hardest. The second is easier. By the fourth, the team is doing it without prompting. By the eighth, the file change concentration metric is down, the dependency graph is acyclic, and the lead time is moving for the first time in a year.

The Bottom Line

The lead-time retro that names "code review" or "approvals" or "sprint discipline" as the bottleneck is misdiagnosing a structural problem as a process problem. The fix for slow reviews is not a faster reviewer — it is a codebase where reviews can be local because changes can be local. The fix for slow deploys is not a better pipeline — it is a codebase where independent changes can deploy independently. The fix for slow lead time is a clean dependency graph.

God classes and circular dependencies are not technical curiosities. They are the structural causes of every coordination event that shows up as waiting in your lead-time chart. Until they are measured, refactored, and prevented from recurring, no amount of process discipline will move the number. The architecture is the schedule. Fix one and the other follows.

Frequently Asked Questions

How do god classes and circular dependencies slow down engineering teams?

God classes serialise work. When one file owns checkout logic, payments, inventory, and user state, every team that touches any of those domains contends for the same file — creating merge conflicts, forced waiting, and a single reviewer (the person who understands the file) becoming a bottleneck for everything. Circular dependencies compound this by making every change ripple. A modification in module A breaks tests in module B, which requires updates in module C, which depends back on A. What should be a one-line fix becomes a five-module change that no engineer can ship alone. Both patterns convert independent work into coordinated work — and coordinated work is where lead time goes to die.

Collapse

What is the relationship between code structure and DORA lead time?

Expand

How can a team measure whether their codebase is causing lead time problems?

Expand

How do you refactor a god class without a rewrite?

Expand

How do you prevent god classes and circular dependencies from coming back?

Expand

Where are the structural choke points in your codebase?

Connect your repo and get a free engineering health diagnosis — file change concentration, dependency analysis, and lead-time root cause in minutes.

Get Your Free Diagnosis

Share this article

Help others discover this content

TwitterLinkedIn
Categories:ArchitectureEngineering PracticesContinuous Delivery