Engineers start builds and go make coffee. They check Slack. They context-switch into another task. By the time the build finishes, they've lost the thread of what they were working on. Thirty minutes of build time doesn't cost you thirty minutes. It costs you the focus of every developer on the team, multiple times a day.

I spent two years embedded in a team dealing with exactly this. The Siemens SIMATIC AX platform, a next-generation automation engineering tool, had a single massive codebase where build times had crept past 30 minutes. The team had tried everything to speed up the builds. More caching. More compute. Different bundlers. Nothing stuck.

The real cost of slow builds

Build time is easy to measure but easy to underestimate. A 30-minute build doesn't just add 30 minutes to a developer's day. It fragments their attention and creates dead time that compounds across the team.

Consider a team of 20 developers, each triggering builds 4–5 times per day. At 30 minutes per build, that's 40–50 hours of waiting time daily. But the real cost is in context switching. Research on developer productivity consistently shows that it takes 15–20 minutes to regain deep focus after an interruption. A 30-minute build creates a 45–50 minute gap in productive work, every time.

Slow builds also change behaviour. Developers batch changes instead of iterating frequently. They test less often. They push larger commits. Larger commits mean harder code reviews, more merge conflicts, and more bugs that slip through. The build time doesn't just slow down delivery. It degrades the entire development workflow.

Why the build is slow

The reflexive response to slow builds is to optimise the build itself. Better caching. Parallel compilation. Faster hardware. Incremental builds. These approaches work up to a point. But when your build is slow because your codebase is a monolith, optimising the build is treating the symptom.

In a monolithic codebase, everything depends on everything. Change one file and the build system has to evaluate the entire dependency graph to figure out what might be affected. Even with incremental builds, the sheer number of connections between modules means most changes touch enough of the graph to trigger a near-full rebuild.

The build isn't slow because the build tool is bad. The build is slow because the codebase makes it impossible for any build tool to be fast. The code was never structured for independent compilation. It grew organically, with different functional areas tangled together in ways that make clean, fast incremental builds impossible.

Modularisation as ownership restructuring

This is the insight that took us from 30 minutes to under five: modularisation is not a build-tool problem. It's an ownership problem.

When one team owns one giant codebase, there's no natural incentive to keep things separated. Everything lands in the same repository. Dependencies creep across boundaries because there are no boundaries. Shared utilities accumulate because it's easier to add a function to the existing utils file than to think about where it really belongs.

Modularisation forces you to answer organisational questions. Who owns this code? Who decides what goes in and what stays out? Who's responsible when it breaks? These aren't technical questions. They're team structure questions. And if you don't answer them, the technical separation won't hold. Someone will add a cross-module dependency because it's convenient, and six months later you're back to a monolith with extra steps.

Identifying natural boundaries

The biggest risk in modularisation is cutting in the wrong places. Split too aggressively and you create a distributed monolith: modules that can't do anything useful without calling five other modules. Split too conservatively and you haven't actually solved the problem.

Natural boundaries tend to follow a few patterns:

Feature areas that change independently. If one part of the application changes frequently while another is stable, that's a boundary. The frequently-changing part benefits from fast builds and independent deployment. The stable part benefits from not being disrupted.

Different user-facing domains. An editor module, a configuration module, a monitoring dashboard. These serve different user needs and typically involve different teams. They're natural candidates for separation.

Shared infrastructure vs. product features. Authentication, logging, design system components, and API clients are infrastructure. They change infrequently and are used everywhere. Product features are the opposite: they change frequently and interact with specific parts of the system. Separating these two layers prevents product churn from destabilising shared infrastructure.

A useful heuristic: if a module can be described in one sentence to a non-technical stakeholder, it's probably the right size. "The module that handles inverter configuration" makes sense. "The module that handles inverter configuration, user authentication, and the notification system" means you probably haven't split enough.

The migration path

You can't stop shipping features for six months while you restructure the codebase. The migration has to happen incrementally, alongside normal product development. Here's the approach that worked on SIMATIC AX:

Map the dependency graph. Before touching any code, understand what depends on what. Visualise the imports, the shared state, the runtime coupling. Most teams are surprised by what they find. Dependencies they thought were clean turn out to be circular. Modules they thought were independent turn out to share hidden state.

Extract the easiest module first. Find the part of the codebase with the fewest inbound dependencies and extract it into its own repository with its own build pipeline. This gives the team practice with the new workflow and proves that the approach works before tackling harder extractions.

Define contracts before splitting. Before you extract a module, define how it communicates with the rest of the system. What events does it emit? What APIs does it expose? What data does it consume? These contracts become the interface that other code depends on, instead of depending on the module's internals.

Move one module at a time. Each extraction should be a bounded, reversible operation. If something goes wrong, you can put it back. As you move modules out, the monolith shrinks and the remaining code becomes easier to reason about. Each step makes the next step simpler.

Don't aim for perfection. The first extraction won't be clean. There will be compromises: a shared dependency that both sides need, a state object that doesn't fit neatly into either module. Accept the messiness and clean it up in subsequent iterations. Waiting for a perfect plan is how modularisation efforts never get started.

Build pipeline architecture

Once code is separated into independent modules, the build pipeline needs to reflect that independence. Each module should have:

  • Its own repository with its own CI pipeline that runs on every commit. Builds that only touch one module should only build that module.
  • Its own test suite that validates the module in isolation. Contract tests verify that the module honours its integration points. Unit and integration tests cover everything else.
  • Its own deployment artifact. A module that passes its tests can be deployed independently, without waiting for other modules to be ready.

The integration layer (the shell application that composes all modules) has its own pipeline that validates the modules work together. But this pipeline should be fast because it's only testing integration, not rebuilding everything.

On SIMATIC AX, this restructuring took the build from over 30 minutes to under five. But the build time was just the measurable outcome. The deeper change was that teams could work independently. A change to the editor didn't require the entire platform to rebuild and redeploy. Teams shipped on their own schedule. The architecture scaled with the organisation instead of against it.

What to watch out for

The distributed monolith. If every module needs every other module to function, you haven't modularised. You've just spread the monolith across multiple repositories. This is worse than the original because now you have the complexity of distribution without the benefits of independence. Clean contracts and minimal coupling are the antidote.

Premature abstraction. Don't build a sophisticated module framework before you've extracted your first module. Start simple. A separate repository with a well-defined build and a clear contract is enough. Add orchestration, versioning, and shared tooling as you learn what you actually need.

Underestimating the cultural shift. Technical modularisation is the easy part. The hard part is changing how teams think about ownership and boundaries. In a monolith, anyone can change anything. In a modular system, changes to another team's code go through their review process. This feels slower at first. It's faster at scale because it eliminates the coordination overhead and surprise breakages that come with shared ownership.

Slow builds are a symptom of unclear ownership and tangled dependencies. Faster hardware and better build tools can mask the problem temporarily, but they can't solve it. Modularisation, real modularisation where code boundaries match team boundaries and each module can be built, tested, and deployed independently, is the structural fix. It takes time. It takes organisational commitment. And it's worth it.