
Modern software systems rarely consist of a single application. A typical product may include a web application, mobile application, backend services, shared libraries, infrastructure scripts, automated tests, documentation, and internal development tools.
As the number of projects grows, development teams must decide how to organize their source code. Should every application and library have its own repository, or should related projects be stored together?
Monorepo architecture addresses this question by placing multiple related projects inside a single source-code repository.
A monorepo can improve collaboration, dependency management, code reuse, testing, and large-scale refactoring. However, simply moving everything into one repository does not automatically produce these benefits. A successful monorepo requires clear project boundaries, automated builds, intelligent testing, ownership rules, and appropriate tooling.
This article explains what monorepo architecture is, how it developed, how it works, its benefits and challenges, and how it can be integrated into an existing software development process.
What Is Monorepo Architecture?
A monorepo, short for “monolithic repository,” is a source-code management strategy in which multiple applications, services, libraries, and tools are stored in a single version-control repository.
For example, an organization might maintain the following projects:
- Customer-facing web application
- Administrative dashboard
- Mobile application
- Authentication service
- Payment service
- Shared user-interface library
- Shared data models
- Infrastructure configuration
- End-to-end tests
- Developer documentation
In a multi-repository environment, each of these projects might have a separate Git repository.
In a monorepo, they could be organized like this:
company-platform/├── apps/│ ├── customer-web/│ ├── admin-dashboard/│ └── mobile-app/├── services/│ ├── authentication-service/│ ├── payment-service/│ └── notification-service/├── libraries/│ ├── shared-ui/│ ├── domain-models/│ └── validation/├── infrastructure/├── documentation/└── tests/ └── end-to-end/
All these projects share the same repository and Git history, but they do not necessarily have to be built, tested, released, or deployed together.
That distinction is important.
A monorepo describes how source code is stored and managed. It does not determine whether the applications are deployed as a monolith, microservices, serverless functions, desktop applications, or independent frontend applications.
Monorepo Does Not Mean One Application
The word “monorepo” is sometimes misunderstood because it contains the word “mono.”
It does not mean that the repository contains only one application.
It means that one repository contains multiple related projects.
A monorepo may include:
- Several independently deployed microservices
- Multiple frontend applications
- Shared libraries
- Infrastructure-as-code projects
- Command-line tools
- Mobile applications
- Documentation websites
- Automated testing frameworks
Each project may have its own build process, deployment pipeline, release schedule, and responsible team.
The repository is shared, but the applications do not have to share the same runtime or deployment lifecycle.
What Is the History Behind Monorepos?
The practice of keeping related software projects in a shared source-control system existed before the term “monorepo” became common.
Earlier centralized version-control systems often encouraged organizations to keep large portions of their code in centrally managed codebases. As software organizations grew, companies began developing custom source-control, build, testing, and dependency-management systems to support very large shared repositories.
Google became one of the most frequently discussed examples. In a 2016 engineering paper, Google described how a large portion of its source code was maintained in a single repository that provided a shared source of truth for tens of thousands of developers. Google also developed specialized infrastructure, including its Piper source-control system and large-scale build tools, to make this model practical.
Meta, formerly Facebook, also invested heavily in large-repository infrastructure. In 2014, Facebook explained how it extended Mercurial to support its growing source code environment. Meta later developed Sapling, a source-control system designed to work with its extremely large monorepo. Meta has highlighted simplified dependency management and the ability to perform broad changes as important reasons for retaining the monorepo model.
Microsoft faced similar challenges when moving the Windows codebase into Git. In 2017, Microsoft described a Windows repository containing approximately 3.5 million files and around 300 GB of repository data. Technologies such as VFS for Git and later Scalar were created to make Git practical for repositories at that scale. Scalar eventually became part of Git itself.
These companies did not invent the general idea of storing multiple projects together. However, their engineering experiences helped popularize the modern monorepo model and demonstrated both its potential and its operational complexity.
Today, tools such as Bazel, Nx, Turborepo, Gradle, Maven, Pants, Buck, Lerna, and package-manager workspaces have made monorepo practices more accessible to smaller organizations.
Why Do We Need Monorepos?
A monorepo is useful when separate projects are strongly related and frequently need to change together.
Imagine that an organization maintains a shared customer data model used by five services and three applications. In a multi-repository setup, changing that model may require:
- Updating the shared library.
- Publishing a new library version.
- Updating several repositories.
- Creating multiple pull requests.
- Coordinating releases across teams.
- Tracking temporary compatibility between versions.
- Removing old code after every consumer has migrated.
In a monorepo, the shared library and its consumers can often be updated in one coordinated change.
The developer can modify the library, update every affected application, run the necessary tests, and submit the entire migration as one pull request.
This does not eliminate the need for architectural discipline, but it reduces coordination overhead.
Monorepos are especially useful when an organization has:
- Many shared libraries
- Closely related applications
- Frequent cross-project changes
- Common build and testing standards
- Multiple teams working on the same platform
- A need for large-scale automated refactoring
- A desire to standardize development practices
However, a monorepo is not automatically the best choice for every organization. Projects that have completely different security requirements, development processes, ownership models, or technology lifecycles may be easier to manage in separate repositories.
What Are the Main Features of a Monorepo?
A monorepo is more than a large Git repository. Mature monorepo environments normally include several supporting capabilities.
Multiple Projects in One Repository
The repository contains several applications, services, libraries, or tools organized into clearly defined directories.
Each project should have an identifiable purpose, owner, dependency list, and build configuration.
Shared Dependency Management
Projects can share internal libraries without publishing every change to an external artifact repository.
JavaScript and TypeScript projects may use npm, pnpm, or Yarn workspaces. Java projects may use Maven multi-module builds or Gradle composite and multi-project builds.
Shared dependency management can make development easier, but teams must still control which projects are allowed to depend on one another.
Project and Dependency Graphs
Modern monorepo tools analyze relationships between applications and libraries.
For example:
customer-web ├── shared-ui ├── authentication-client └── domain-modelspayment-service ├── domain-models └── event-library
The dependency graph helps the build system understand which projects may be affected by a change.
Nx, for example, uses project graphs and task graphs to understand project relationships, task ordering, caching, and affected projects. Turborepo similarly creates package and task graphs from the repository structure and configuration.
Affected-Project Detection
A basic continuous integration pipeline might rebuild and retest every project after every commit.
That approach becomes too slow as the repository grows.
Monorepo tools can compare Git revisions, identify the changed files, map those files to projects, analyze downstream dependencies, and run tasks only for the affected portion of the repository.
For example, changing a library used by two applications might trigger tests for:
- The changed library
- The two applications that depend on it
- Relevant integration tests
Unrelated applications would not need to be rebuilt.
Nx provides affected commands that use Git changes and the project graph to calculate the minimum set of projects that require a task.
Build and Test Caching
Monorepo tools can calculate a hash from source files, dependencies, environment variables, commands, and configuration.
When the inputs have not changed, a previously generated build or test result can be reused.
The cache may be local to a developer’s computer or shared remotely across the entire development team and CI environment.
Bazel and other modern build systems support remote caching so that developers and CI agents can reuse compatible build outputs instead of repeating the same work.
Task Orchestration
Tasks must run in the correct order.
For example:
Build shared library ↓Build authentication service ↓Run service tests ↓Build customer application ↓Run end-to-end tests
A monorepo task runner creates a task graph, runs independent tasks in parallel, and ensures dependent tasks execute in the correct sequence.
Shared Development Standards
A monorepo can provide centralized configuration for:
- Code formatting
- Static analysis
- Security scanning
- Testing frameworks
- Compiler settings
- Dependency versions
- Pull request templates
- CI/CD workflows
- Code-generation tools
- Documentation standards
This makes it easier to introduce organization-wide improvements.
Code Ownership and Boundaries
Although the repository is shared, every developer should not automatically approve or modify every project.
Ownership can be assigned by directory:
/apps/customer-web/ Frontend Team/services/payment/ Payments Team/libraries/domain-models/ Platform Team/infrastructure/ DevOps Team
GitHub’s CODEOWNERS feature can automatically request reviews from the teams responsible for particular paths.
Architecture rules should also prevent projects from creating unauthorized dependencies.
For example, a user-interface library should not directly access a database module, and one business domain should not silently depend on another domain’s internal implementation.
How Does a Monorepo Work?
A monorepo usually combines source control, workspace management, project metadata, dependency analysis, build orchestration, testing, and release automation.
Consider the following simplified workflow.
Step 1: A Developer Makes a Change
A developer modifies the shared validation library:
/libraries/validation/
Step 2: The Monorepo Tool Evaluates Dependencies
The project graph shows that the validation library is used by:
- Customer web application
- Administrative dashboard
- Registration service
Step 3: Relevant Tasks Are Selected
The development or CI system runs:
- Validation library unit tests
- Customer web application tests
- Administrative dashboard tests
- Registration service tests
- Relevant integration tests
Unrelated projects are skipped.
Step 4: Cached Results Are Reused
Tasks whose inputs have not changed may reuse outputs from the local or remote cache.
Step 5: The Change Is Reviewed as a Unit
The pull request contains the library modification and any required consumer updates.
This creates an atomic change: the repository is not temporarily left with a new library version and incompatible consumers.
Step 6: Projects Are Released
The affected applications can then be versioned and deployed according to their individual release processes.
Being in the same repository does not require all projects to be deployed together.
Benefits of Monorepo Architecture
Atomic Cross-Project Changes
One pull request can update a shared library and all its consumers.
This prevents situations where a new interface is published before every dependent project is ready to use it.
Easier Large-Scale Refactoring
Developers can search the entire platform, change an API, update its consumers, and validate the migration together.
This is especially valuable when removing deprecated functions, changing shared models, applying security fixes, or upgrading frameworks.
Google’s research into monolithic codebases found that repository-wide visibility helps developers discover reusable APIs, find usage examples, and update dependent code during migrations.
Improved Code Visibility
Developers can see how other teams solve similar problems.
This may reduce duplicated libraries and encourage the use of existing components.
Visibility must be supported by documentation and architectural guidance. Otherwise, developers may copy code without understanding its ownership or intended use.
Consistent Development Standards
Formatting, testing, security, dependency, and CI policies can be managed centrally.
An organization-wide rule can often be introduced through one coordinated change instead of updating dozens of repositories independently.
Easier Code Reuse
Shared libraries are immediately available to applications in the workspace.
Developers can update a library and test it against real consumers before merging the change.
Simplified Dependency Coordination
Internal projects do not always need to publish temporary package versions just to test related changes.
A monorepo can also make it easier to identify conflicting or outdated dependencies.
Better Developer Onboarding
A new developer can clone one repository and explore the relationships among applications, services, libraries, tests, and infrastructure.
A well-designed monorepo can provide standard commands such as:
buildtestlintstartformataffected
The same commands can work consistently across many projects.
Improved Continuous Integration
Dependency-aware CI pipelines can build and test only the projects affected by a change.
Caching, parallel execution, and distributed task execution can further reduce pipeline duration.
Centralized Governance
Licensing checks, security scanning, quality gates, code ownership, and dependency policies can be applied from a central location.
Challenges of Monorepo Architecture
Monorepos solve coordination problems, but they also introduce new technical and organizational challenges.
Repository Performance
As the repository grows, operations such as cloning, fetching, checking status, switching branches, and analyzing files may become slower.
Git provides capabilities such as partial clone, sparse checkout, sparse index, background maintenance, and Scalar to improve performance for very large repositories. Scalar is specifically designed to configure Git for large-repository workloads.
Most organizations will not reach the scale of Google, Meta, or Microsoft. Nevertheless, repository performance should be measured before it becomes a serious problem.
Slow CI Pipelines
Running every build and test after every change can make a monorepo impractical.
The solution is not simply to purchase larger CI machines. Teams should implement:
- Affected-project detection
- Local and remote caching
- Parallel execution
- Distributed builds
- Test categorization
- Incremental compilation
- Reliable dependency graphs
Accidental Coupling
Because all the code is easily accessible, developers may create direct dependencies between unrelated projects.
Over time, the repository can become a tangled dependency network.
Module-boundary rules, dependency constraints, architecture tests, and code review policies are necessary to preserve separation.
Unclear Ownership
A shared repository can create confusion about who owns a directory, library, or service.
Every project should have documented owners, maintainers, support expectations, and review requirements.
Broad Access to Source Code
Some organizations must restrict access to sensitive projects.
Repository-level permissions are often easier to manage than fine-grained directory permissions. If teams are legally or contractually prohibited from viewing certain source code, keeping everything in one repository may not be appropriate.
Separate repositories may still be necessary for security-sensitive or externally maintained projects.
Release Complexity
Applications in a monorepo may have independent release schedules.
Teams must decide whether to use:
- One shared repository version
- Independent project versions
- Synchronized releases
- Change-based versioning
- Release manifests
- Automated changelog generation
The repository model does not answer these questions automatically.
Shared Configuration Can Become Restrictive
Centralized standards are useful, but excessive standardization can prevent teams from choosing tools appropriate for their projects.
A good monorepo establishes sensible defaults while allowing carefully controlled exceptions.
Large Pull Requests
Cross-project changes can become difficult to review.
Large migrations should be automated where possible and divided into understandable phases. Generated changes should be clearly separated from behavioral changes.
A Broken Main Branch Has a Wider Impact
When many teams share the same primary branch, one incompatible change can affect a large portion of the organization.
Strong pull request validation, branch protection, reliable tests, and merge queues become increasingly important. GitHub describes merge queues as a way to validate changes against the latest target branch and reduce incompatible merges in busy repositories.
Monorepo vs. Multi-Repo
Neither model is universally superior.
A Monorepo May Be Better When:
- Projects frequently change together.
- Teams share many internal libraries.
- Organization-wide refactoring is common.
- Development standards should be consistent.
- Cross-project visibility is valuable.
- The organization can invest in build and CI tooling.
Multiple Repositories May Be Better When:
- Projects are largely independent.
- Different access restrictions are required.
- Teams have unrelated release processes.
- Products use completely different technology ecosystems.
- External organizations maintain individual components.
- Repository-level isolation is more important than code sharing.
Some organizations use a hybrid approach.
For example, a company may maintain:
- One monorepo for its main product platform
- A separate repository for infrastructure
- Separate repositories for open-source projects
- Separate repositories for security-sensitive systems
- Separate repositories for experimental applications
The correct repository boundary should reflect real collaboration, ownership, security, and dependency relationships.
Is a Monorepo Similar to a Modular Monolith?
A monorepo and a modular monolith can appear similar because both encourage organization, shared standards, code reuse, and clearly defined boundaries.
However, they address different architectural concerns.
Monorepo
A monorepo is a source-code repository strategy.
It answers questions such as:
- Where is the source code stored?
- How are projects versioned in source control?
- How are shared builds and tests coordinated?
- How are cross-project changes reviewed?
- Which projects are affected by a commit?
Modular Monolith
A modular monolith is an application architecture and deployment strategy.
It answers questions such as:
- How is one application divided into business modules?
- How do modules communicate?
- How are module boundaries enforced?
- Is the application deployed as one unit?
- How is data ownership separated inside the application?
A modular monolith normally contains multiple internal modules but is built and deployed as one application.
A monorepo may contain:
- A modular monolith
- Several microservices
- Multiple frontend applications
- Shared libraries
- Infrastructure code
- Mobile applications
- Testing tools
Therefore, a monorepo is not an alternative to a modular monolith.
They can be used together.
For example:
platform-monorepo/├── apps/│ ├── commerce-modular-monolith/│ │ ├── customer-module/│ │ ├── order-module/│ │ ├── payment-module/│ │ └── inventory-module/│ └── admin-portal/├── services/│ └── notification-service/└── libraries/ └── shared-observability/
In this example, the entire platform uses a monorepo, while the commerce application uses modular monolith architecture.
To learn more about modular monolith architecture, read:
What Is a Modular Monolith?
https://swenotes.com/2025/09/17/what-is-a-modular-monolith/
How to Integrate a Monorepo into the Software Development Process
Moving to a monorepo should be treated as an engineering transformation rather than a simple repository merge.
1. Identify the Problem You Are Trying to Solve
Do not adopt a monorepo only because large technology companies use one.
Document the current problems:
- Are shared library updates difficult?
- Do cross-repository changes require excessive coordination?
- Are teams duplicating tools and configuration?
- Are dependency versions inconsistent?
- Are developers unable to test related changes together?
- Is organization-wide refactoring too difficult?
The expected benefits should be measurable.
2. Select an Appropriate Scope
The first monorepo does not need to contain every project in the organization.
Begin with a group of applications and libraries that:
- Share a business domain
- Frequently change together
- Have similar access requirements
- Use compatible development processes
- Are maintained by collaborating teams
3. Create a Clear Directory Structure
Organize projects according to responsibilities rather than allowing teams to create arbitrary folders.
A common structure might be:
repository/├── applications/├── services/├── libraries/├── tools/├── infrastructure/├── documentation/└── tests/
The directory structure should communicate architectural intent.
4. Define Project Boundaries
Document which dependencies are allowed.
For example:
- Applications may depend on shared libraries.
- Domain libraries may not depend on applications.
- One domain may access another domain only through a public interface.
- Infrastructure utilities may not contain business logic.
- Internal implementation packages may not be imported by other teams.
Automated architecture tests should enforce these rules.
5. Standardize Common Commands
Provide predictable commands for common tasks:
./build./test./lint./format./start./affected
Developers should not need to memorize a completely different workflow for every project.
6. Introduce Dependency-Aware Builds
Create a reliable project graph.
The system should understand:
- Which project owns each file
- Which projects depend on other projects
- Which tasks produce reusable outputs
- Which tests validate each dependency
- Which applications are affected by a change
Without this information, the CI system may either run too much or fail to test important consumers.
7. Optimize Continuous Integration
Start by measuring:
- Average pipeline duration
- Cache hit rate
- Number of projects tested per change
- Queue time
- Failure rate
- Flaky test rate
- Cost per pipeline
Then introduce:
- Affected-project testing
- Parallel execution
- Remote caching
- Distributed workers
- Incremental builds
- Test splitting
- Merge queues
8. Define Code Ownership
Assign owners to applications, services, libraries, infrastructure, and shared configurations.
Ownership should determine:
- Required reviewers
- Maintenance responsibility
- Incident responsibility
- Approval for breaking changes
- Deprecation decisions
- Architecture exceptions
9. Decide How Releases Will Work
Determine whether projects will be released together or independently.
For independent releases, the automation should:
- Identify affected projects.
- Calculate version changes.
- Generate release notes.
- Build the required artifacts.
- Publish packages or images.
- Deploy only the selected applications.
10. Migrate Incrementally
Avoid combining every repository in a single high-risk migration.
A safer approach is to:
- Create the monorepo structure.
- Move a small group of related libraries.
- Preserve their Git history where practical.
- Establish build and testing conventions.
- Move one application.
- Validate developer and CI performance.
- Improve the tooling.
- Migrate additional projects gradually.
11. Measure the Results
Track whether the monorepo is solving its intended problems.
Useful measurements include:
- Time required for cross-project changes
- Build duration
- Test duration
- Developer setup time
- Number of duplicated dependencies
- Frequency of broken builds
- Time required for framework upgrades
- Repository operation performance
- Developer satisfaction
The repository should evolve based on evidence rather than assumptions.
Monorepo Best Practices
A successful monorepo should follow several principles:
- Organize projects around clear responsibilities.
- Treat shared libraries as maintained products.
- Enforce dependency and module boundaries.
- Run only affected builds and tests.
- Use local and remote caching.
- Assign ownership by project or directory.
- Keep generated files and large binary artifacts out of normal Git history.
- Automate dependency upgrades.
- Protect the primary branch.
- Keep pull requests focused and reviewable.
- Document how projects are built, tested, released, and deployed.
- Measure repository and CI performance continuously.
- Allow justified exceptions to shared standards.
- Avoid turning the shared repository into a shared runtime architecture.
When Should You Avoid a Monorepo?
A monorepo may not be appropriate when:
- Source code must be isolated for legal or security reasons.
- Projects are owned by unrelated organizations.
- Applications have almost no shared dependencies.
- Teams require completely different source-control workflows.
- The organization cannot invest in CI and build optimization.
- The repository would create more coordination than it removes.
- Independent teams need strong repository-level autonomy.
A poorly managed monorepo can become a large, slow, tightly coupled codebase.
A well-managed multi-repository environment is better than an unstructured monorepo.
Conclusion
Monorepo architecture stores multiple applications, services, libraries, tools, and supporting resources in one source-code repository.
Its most important benefits include atomic cross-project changes, easier refactoring, improved code visibility, consistent development standards, simpler dependency coordination, and more intelligent continuous integration.
However, monorepos also create challenges related to repository performance, build duration, access control, ownership, accidental coupling, and release management.
The most important lesson is that a monorepo is not simply a large folder containing every project.
It is a development platform that requires:
- Clear architectural boundaries
- Dependency-aware tooling
- Automated testing
- Build caching
- Code ownership
- Release automation
- Governance
- Continuous performance measurement
A monorepo is also not the same as a modular monolith. A monorepo determines where source code is stored, while a modular monolith determines how an application is structured and deployed.
Organizations can use either concept independently, or they can place a modular monolith alongside other applications and services inside a larger monorepo.
When introduced for the right reasons and supported by appropriate engineering practices, a monorepo can reduce coordination costs and make complex software platforms easier to understand, change, test, and maintain.

Recent Comments