Marcio Cunha

How to speed up monorepo CI pipelines with incremental builds

Learn how to build fast continuous integration pipelines in monorepos using incremental builds and selective test execution based on directory changes.

Marcio Cunha•5 min
Also available in:PortuguêsEspañol
Summary
  • Monorepos centralize multiple projects in a single repository but create severe time bottlenecks in CI pipelines without intelligent automation
  • Selective test execution identifies exactly which packages changed in the current commit to avoid running redundant tests across the entire codebase
  • Modern orchestration tools build a detailed dependency graph to map the exact impact of every code change
  • Caching build artifacts reuses previous compilation results when source code remains unchanged
  • The dramatic reduction in pipeline feedback time improves developer productivity and lowers cloud infrastructure costs

The challenge of monorepos in continuous integration

Working with multiple projects inside a single code repository, a practice known as a monorepo, brings incredible advantages for code sharing and versioning. However, when it is time to run continuous integration, the system often suffers from extreme slowness. In practice, this means altering a single documentation file can trigger the test suite and compilation process for the entire company ecosystem. To mitigate this problem without losing test safety, teams must adopt smart strategies that prevent wasting time and computational resources.

As code volume grows, the traditional approach of running everything to ensure nothing broke becomes financially unsustainable and frustrating for engineers. The waiting time between pushing code to the server and receiving confirmation that everything is correct can jump from minutes to hours. This operational bottleneck reduces delivery cadence and discourages small improvements in the codebase. If you have ever faced slowness in CI servers, you might notice similarities to hardware problems under stress, as we describe in our analyses on how to test CPU and VRM thermal stress when the system overheats due to inadequate isolation.

How selective test execution works

Selective test execution is the practice of analyzing which exact files changed in the current commit and running only the tests directly affected by those modifications. Instead of running the entire suite, the CI system calculates a diff between the current commit and the previous state of the main branch. If only the authentication module was modified, for example, the pipeline skips tests for payment services, reporting, and the user interface. In practice, this means feedback time drops from tens of minutes to mere seconds on targeted changes.

To implement this logic, engineers use tools capable of mapping cross-dependencies between internal packages. If package A depends on package B, any change in B requires package A to be tested again, since the contract between them might have broken. This intelligence prevents false positives of success when the impact of a change is indirect. Precise mapping ensures the pipeline remains fast without sacrificing the reliability needed for production.

Creating a dependency graph for incremental builds

The concept of incremental builds relies on never recompiling or retesting anything that has not been modified since the last successful execution. To achieve this goal, the CI tool builds a directed acyclic graph, a mathematical structure that connects code blocks according to their import dependencies. Each package in the monorepo becomes a node in this graph, and the arrows indicate who depends on whom. When a developer pushes a commit, the orchestrator traverses the graph starting from the modified nodes, marking only the dependent neighbors for a new execution.

Besides selecting what runs, the system caches the results of previous steps, such as compiled binaries and test artifacts. If the source code hash of a module is identical to the hash stored in the remote cache, the build step is simply skipped and the saved artifact is retrieved instantly. This approach eliminates redundant calculations and optimizes hardware resource usage. The time saved is similar to network infrastructure optimization, where proper automation prevents operational bottlenecks, as discussed in our guide on how to automate switches with Ansible in local networks.

Step-by-step guide to configuring an incremental pipeline

To put these techniques into practice daily, we need to structure the workflow using modern tools like Turborepo, Nx, or Bazel. Below is a practical configuration example using a task definition file to manage dependencies and cache automatically in a monorepo.

{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "inputs": ["src/**", "test/**"]
    },
    "lint": {
      "outputs": []
    }
  }
}

With this basic configuration, the orchestrator understands that a package build task depends on the build of its upstream neighbors, while tests only run after successful compilation completion. The declared inputs and outputs allow the system to compute precise cryptographic hashes to decide whether an artifact needs recreation or retrieval from the cache. This structural discipline ensures no unnecessary step runs during the integration cycle.

Best practices and common pitfalls in monorepos

Although selective execution brings expressive speed gains, maintaining a monorepo requires rigorous discipline to prevent silent failures. The most common error is creating hidden dependencies between packages that are not explicitly declared in package manager or build tool configuration files. When this happens, the dependency graph fails to notice that a change in an isolated directory broke a distant module, resulting in errors that only appear in production. Another critical point is the correct management of shared caches between developers and the CI server.

Ensuring the cache is deterministic is fundamental to preventing the pipeline from failing on a developer machine and passing on the server, or vice-versa. CI tools require consistent environment variables and operating system isolation so generated hashes accurately reflect the real state of the code. When the ecosystem reaches high maturity, database performance issues correlated with the data volume generated by parallel tests also demand attention, similar to the care needed when we explain how to fix connection pools under heavy traffic.

Final considerations

Speeding up CI pipelines in monorepos is not just about optimizing time, but preserving developer mental health and business agility. By implementing incremental builds, directory-change-based selective test execution, and smart artifact caching, organizations can scale their codebases without sacrificing delivery speed. The initial investment in configuring these tools pays off quickly through the dramatic reduction in waiting times and continuous integration server costs.

Ultimately, a fast pipeline transforms software engineering culture, enabling short feedback cycles and more frequent deploys with total confidence. Adopting these practices requires planning and standardization, but the results reward every effort put into refactoring the integration architecture.