A monorepo is a single version control repository that holds the source code for multiple distinct projects, services, or applications, rather than splitting each one into its own repository. A company might keep its web frontend, backend services, shared libraries, and internal tooling all in one repository with one commit history, instead of maintaining a dozen separate repositories that each need their own release process and versioning. The term is really about repository structure, not about the architecture of the software itself. A monorepo can hold a single monolithic application or dozens of independently deployable microservices, the repository layout is a separate question from how the code is deployed.
The reason monorepos exist is that splitting code into many small repositories creates real coordination costs once a change needs to touch more than one of them. If a shared library lives in its own repository, every consumer needs to update a version number, publish, wait, and then bump their own dependency to pick up the change, a process that can take days for something that should take minutes. Multi-repo setups also make it hard to see the full picture of a change: a single logical fix might require opening five pull requests across five repositories, each reviewed in isolation, with no single view of the entire change. Monorepos collapse all of that into one commit, one pull request, one CI run.
What distinguishes a monorepo from simply having a large codebase is the tooling built around it. A naive single repository with thousands of projects would have every CI run rebuild and retest everything, which becomes unworkable at scale. Real monorepo setups use build systems designed specifically for this, tools like Bazel, Nx, Turborepo, or Buck, that understand the dependency graph between projects and only build or test what actually changed and what depends on it. This incremental, graph-aware approach is what makes a monorepo practical past a certain size, without it, a monorepo just becomes a slow, unwieldy single repository.
By 2026, monorepos are common well beyond the handful of companies, Google, Facebook, and a few others, that were associated with the pattern a decade ago. Frontend-focused monorepo tools like Turborepo and Nx have made the pattern accessible to small and mid-size teams managing a handful of related packages, not just organizations with thousands of engineers. At the same time, plenty of successful companies run polyrepo setups deliberately, and the debate has matured past "monorepo is better" into a more specific conversation about what problem a given team is actually trying to solve.
This page covers why monorepos exist, how the tooling that makes them workable actually functions, how monorepos differ from and relate to microservices architecture, where a monorepo fits and where a polyrepo is the better call, and how a team actually migrates into one without breaking everything along the way. The durable idea underneath it is that repository structure is a tool for managing coordination cost, and understanding that lets a team choose the structure that matches how their code actually needs to move together, rather than following a trend.
The core technical challenge a monorepo has to solve is scale. Once a repository contains dozens or hundreds of projects, you cannot afford to rebuild and retest all of them on every commit, that would make CI runs take hours and grind development to a halt. Monorepo build tools solve this by constructing a dependency graph of the codebase, understanding which packages depend on which others, and then computing exactly which projects are affected by a given change. If you touch a shared authentication library, the tool knows every service that depends on it and rebuilds and tests only those, leaving the rest of the repository untouched.
This graph-aware approach also enables caching in a way flat repositories can't match. Tools like Bazel and Nx can cache the output of a build or test step keyed on the exact inputs that produced it, so if nothing relevant has changed, the tool serves a cached result instantly rather than rerunning the work. Some of these tools support remote caching shared across the whole team, so if one engineer already built a particular package with a particular set of inputs, nobody else on the team has to rebuild it from scratch, they just pull the cached result. This is often where the biggest, most visible speed gains show up for engineers day to day.
Dependency graph awareness also protects against a subtler problem, silent drift between packages that are supposed to stay in sync. In a multi-repo setup, it is entirely possible for a shared library to be updated and for some consumers to lag behind on an old version for months, sometimes accumulating security or compatibility problems nobody notices until they cause an incident. In a monorepo, there is only one version of the shared library, at any moment, and every consumer is building against that same version. This forces a kind of continuous integration, in the literal sense, that multi-repo setups have to work much harder to achieve.
None of this comes for free. Setting up a monorepo build system well is genuinely engineering work: defining the project graph correctly, configuring caching, and setting up CI to only run affected projects rather than everything. Teams that adopt a monorepo without this tooling investment, just dropping many projects into one Git repository, often end up worse off than before, with slow CI and a codebase that's hard to reason about, which is the case most often cited by engineers who tried monorepos and went back to separate repositories.
There is also a question of who owns and maintains this build tooling once it's in place, since it tends to become critical shared infrastructure that every team depends on but that no single product team is naturally incentivized to invest in improving. Organizations running monorepos at meaningful scale usually end up with a dedicated platform or developer experience function responsible for the health of the build system itself, treating slow builds or unreliable caching as an incident worth fixing quickly, in much the same way they'd treat a production outage, because a broken build system stalls every team at once rather than just one.
A common point of confusion is treating "monorepo" as the opposite of "microservices," when they answer different questions entirely. Monorepo versus polyrepo is a question about where code lives in version control. Monolith versus microservices is a question about how software is deployed and how services communicate with each other at runtime. You can have a monorepo containing a single deployed monolith, a monorepo containing forty independently deployed microservices, a polyrepo where each repository is its own microservice, or a polyrepo containing a handful of repositories that together deploy as one monolith. All four combinations exist in production systems today.
Companies with large microservices architectures, in fact, are some of the strongest adopters of monorepos, precisely because microservices create the exact coordination problem monorepos are good at solving. If you have sixty services that all depend on a shared internal SDK, and each service lives in its own repository, updating that SDK across all sixty services one dependency bump at a time is a genuinely painful, error-prone process. Putting all sixty services in one monorepo means the SDK update and all sixty consumer updates can happen in a single commit, validated by CI in one pass, deployed independently per service but coordinated as one logical change.
This pairing isn't universal, though. Some organizations deliberately keep each microservice in its own repository specifically because they want strong isolation, separate access control, separate release cadences, and a smaller blast radius if something in the repository's tooling breaks. A team that owns one service in a polyrepo setup can largely ignore what forty other teams are doing, whereas in a monorepo, everyone is at least nominally sharing infrastructure, conventions, and a single source of truth for what "current" means.
There's also a nuance around release independence that's easy to miss. Having all your microservices in one repository does not mean they all have to deploy together, most monorepo setups explicitly support independent deployment pipelines per service, triggered only when that service's own code, or something it depends on, actually changes. The monorepo affects where code is stored and how changes are validated together, it doesn't by itself dictate a shared release train, and conflating the two is a common source of unnecessary anxiety when teams first consider adopting the pattern.
The practical takeaway is that deciding on a monorepo doesn't answer, or need to answer, the architecture question at all. A team should design its service boundaries and deployment topology based on the problem domain and organizational structure, and separately decide, based on how much shared code and cross-team coordination actually happens, whether one repository or many is the better fit for storing that code. Treating these as one combined decision tends to produce worse outcomes on both fronts, since a team that picks its service boundaries to fit a repository structure, rather than the other way around, usually ends up with services that don't map cleanly to anything real in the business.
Monorepos fit well for organizations with significant code sharing between projects, particularly around internal libraries, design systems, or type definitions that many teams consume. Frontend ecosystems using TypeScript are a strong fit for this reason, a shared component library or a shared types package benefits enormously from living in the same repository as the applications that consume it, since a change to a shared type can be validated against every consumer in the same pull request, catching breakage immediately rather than after a separate package is published and later adopted.
They also fit well for organizations that want atomic, cross-cutting changes to be possible as a matter of course. A large-scale refactor, a security fix that needs to touch every service, or a rename that spans a hundred files across a dozen projects, these are straightforward in a monorepo because they can happen in a single commit with a single CI validation. In a polyrepo world, the same change requires opening and merging a coordinated set of pull requests across many repositories, tracking their interdependencies manually, and hoping nothing lands out of order.
Monorepos fit less well when an organization genuinely needs strong isolation between teams or projects, whether for security reasons, such as different repositories needing different access controls for compliance purposes, or for organizational reasons, such as a company acquiring another company and not wanting to immediately merge codebases with very different conventions and histories. They also fit less well for organizations without the engineering capacity to invest in the build tooling a monorepo needs at scale, since a monorepo without proper tooling degrades into slow, unwieldy CI runs that frustrate every engineer touching the repository.
Very large-scale monorepos, the kind Google and Meta run with billions of lines of code, also require custom, heavily invested-in infrastructure that most companies will never need to replicate, and that scale of investment isn't a realistic template for a fifty-person startup. The lesson from those companies isn't "build what Google built," it's "the dependency graph and incremental build approach is the right idea," which smaller, off-the-shelf tools like Nx and Turborepo already implement at a scale appropriate for most teams.
There is also a middle category worth naming honestly: organizations somewhere between a five-person startup and a five-thousand-person tech company, often in the low hundreds of engineers, where the calculus genuinely could go either way. These teams often benefit from piloting a monorepo with a subset of related projects, the ones with real shared code and frequent cross-team changes, while leaving genuinely independent projects in their own repositories, rather than treating the decision as all-or-nothing across the entire engineering organization.
One question every organization runs into once a monorepo grows past a handful of teams is who actually gets to approve a change. In a polyrepo world, ownership is implicit in the repository boundary itself, if you don't have write access to a repository, you can't merge into it, and that's usually enough. A monorepo removes that free structural boundary, since everyone technically has the whole codebase checked out locally and, absent other controls, could open a pull request touching any part of it. Most mature monorepo setups solve this with path-based ownership files that map directories to the teams responsible for them, so a change under the payments team's directory automatically requires a review from someone on that team, regardless of who authored the change.
This ownership model needs to be treated as living infrastructure, not a one-time setup task. As teams reorganize, projects get renamed, and new services get added, the ownership mapping has to be kept current or it silently stops doing its job, either blocking legitimate changes because an owner mapping points to a team that no longer exists, or failing to require review from the team that should actually be looking at a change. Organizations that run monorepos well typically assign clear responsibility for keeping this mapping accurate, often as part of a platform or developer experience team's remit, rather than leaving it to accumulate drift.
Broad code visibility, the fact that engineers can see and search across the entire company's codebase by default in a monorepo, is usually treated as a benefit rather than a risk for most internal engineering work. It lets an engineer on one team find and learn from how a similar problem was solved elsewhere in the company, and it makes onboarding easier since there's one place to look for anything. That said, some organizations have specific categories of code, security-sensitive configuration, compliance-relevant logic, or code tied to unreleased business initiatives, where broad visibility genuinely isn't appropriate, and monorepo tooling generally supports carving out narrower access for those specific areas rather than requiring an all-or-nothing choice.
Cost and resource attribution is a less obvious but real consideration at scale. When every team's code lives and builds within one shared repository and one shared CI system, it can become harder to see which team's tests are consuming the most compute time, or whose changes are most often responsible for slowing down the shared build. Organizations running large monorepos increasingly instrument their build systems specifically to attribute CI cost and build time back to individual teams or projects, both to identify optimization opportunities and to keep the incentives sane, so that no single team can degrade shared infrastructure without that cost being visible to them.
The most common approach to migrating existing separate repositories into a monorepo is to preserve history where possible, using tooling like git subtree or dedicated migration scripts that merge each repository's commit history into a subdirectory of the new monorepo. This matters more than it might seem, losing commit history means losing the ability to trace why a particular line of code exists, which blame and archaeology tools depend on, and engineers notice and feel the loss quickly when it's gone.
Before merging repositories together, it's worth establishing the build tooling and CI pipeline in the new monorepo using a small, representative subset of the projects first, rather than dumping everything in at once and discovering the CI setup doesn't scale. Getting the dependency graph configuration right, confirming affected-project detection actually works, and validating that caching produces correct results are all things you want to shake out on a small scale before the whole organization depends on the new repository working correctly on day one.
Sequencing the migration itself deserves thought too. Some organizations migrate everything in one coordinated cutover, which minimizes the time spent maintaining two parallel systems but concentrates all the risk into a single event. Others migrate incrementally, moving one project at a time into the monorepo while the rest continue operating as separate repositories, which spreads the risk out but means living with a hybrid setup, and the tooling to support cross-repository dependencies during that transition, for a longer stretch of time. Neither approach is universally correct, the right choice depends on how much engineering capacity a team can dedicate to the migration itself without neglecting ongoing product work.
Finally, teams should expect a period where CI feels slower or flakier than before, simply because more projects and more contributors are now hitting a shared CI system rather than several independent, smaller ones. This is usually a signal to invest further in caching and in properly scoping which tests run for which changes, not a reason to abandon the migration. Teams that treat the first few weeks after migration as a tuning period, rather than a verdict on whether the monorepo was the right call, generally end up in a much better place than teams that give up at the first sign of friction.
A monorepo is a single version control repository that holds the code for multiple projects, services, or applications, rather than splitting each one into its own separate repository, allowing shared code and cross-project changes to be managed and validated together.
No, they answer different questions. A monorepo is about where code lives in version control, while a monolith is about how software is architected and deployed. A monorepo can contain many independently deployed microservices, and a monolith's code can just as easily live across several separate repositories.
Common tools include Bazel and Buck for large-scale, language-agnostic setups, and Nx and Turborepo for JavaScript and TypeScript focused monorepos. These tools build a dependency graph of the projects in the repository and only build or test what a given change actually affects.
Mainly to reduce coordination costs. Shared libraries can be updated and validated against every consumer in a single commit, cross-cutting refactors can happen atomically, and there's one source of truth for what version of shared code is currently in use, rather than multiple repositories drifting out of sync.
Without proper build tooling, CI can become slow as the repository grows, since naively rebuilding and retesting everything on every change doesn't scale. Monorepos can also reduce isolation between teams and require more deliberate access control than several fully separate repositories would.
Yes. Language-agnostic build tools like Bazel are specifically designed to handle multiple languages within one dependency graph, and many real-world monorepos mix Python, Go, TypeScript, and other languages across different projects in the same repository.
They build heavily customized internal tooling, including custom build systems and often custom version control systems, to handle billions of lines of code. Most organizations don't need to replicate this level of investment, off-the-shelf tools like Nx or Bazel already bring the core dependency-graph approach to a scale appropriate for far smaller teams.
It takes real planning, particularly around preserving commit history, setting up build tooling correctly, and establishing access control, but it's a well-trodden path with established tooling. Piloting the migration on a small subset of projects before moving everything tends to reduce the risk significantly.
Often yes, particularly if the startup has several related projects, like a frontend and backend that share types or utilities. Modern tools like Turborepo and Nx make monorepos practical at small scale without requiring the heavy infrastructure investment large companies need.