Quick Answer

A monorepo holds several projects in one repository with shared tooling and history. It makes changing shared code atomic, at the cost of needing tooling to avoid rebuilding everything on every change.

The problem with separate repositories

You have a web app, a mobile app and a shared component library, in three repositories. You need to change a component's props.

The sequence: change the library, bump its version, publish it, open a pull request in the web app to upgrade, another in the mobile app, and merge all three. Until they are all merged, the version deployed to production is inconsistent across your own products.

If you got the change wrong, repeat the whole cycle. If someone forgets to upgrade one consumer, it silently stays on the old version until something breaks.

In a monorepo that is one commit. The library and both consumers change together, CI tests all three, and the change either merges completely or not at all.

That atomicity is the entire argument. Everything else is a consequence.

What it looks like

my-company/
  package.json          <- workspace root
  apps/
    web/
      package.json
    mobile/
      package.json
  packages/
    ui/
      package.json
    config/
      package.json

Each app and package keeps its own package.json. The root declares the workspace:

{
  "private": true,
  "workspaces": ["apps/*", "packages/*"]
}

npm, pnpm and yarn all support workspaces natively. Installing at the root installs everything once, hoisting shared dependencies, and links local packages to each other — so apps/web importing @company/ui resolves to the local folder rather than a published version.

That linking is what makes editing a shared package feel immediate rather than requiring a publish cycle.

Why you need extra tooling

The naive monorepo has an obvious problem: a one-line change to the mobile app triggers CI that builds and tests everything, including packages that could not possibly be affected. As the repository grows, that becomes intolerable.

Monorepo tools — Turborepo, Nx and similar — solve it two ways:

  • Dependency-aware task running. They understand which packages depend on which, so changing packages/ui rebuilds only the things that consume it.
  • Caching. If a package's inputs have not changed, reuse the previous output instead of rebuilding. With a shared remote cache, a colleague's build result can be reused by CI, so it never builds the same thing twice.

Caching is what makes large monorepos practical. Without it, the build time argument against monorepos is entirely correct.

The costs, honestly

  • Tooling complexity up front. Workspaces, task orchestration and caching are all configuration you would not otherwise need.
  • Repository size. Everyone clones everything. Very large monorepos need specific git strategies to stay usable.
  • Access control is coarse. Repository-level permissions mean everyone with access sees everything. Separate repositories give finer control.
  • Coupling by accident. Because importing across packages is easy, unintended dependencies creep in. Enforcing boundaries becomes a discipline rather than a technical barrier.
  • CI must be configured well or you rebuild the world on every commit.

A monorepo is not the same as a monolith. You can deploy each app independently from a monorepo; the repository layout and the deployment architecture are separate decisions, and conflating them is a common confusion — see microservices vs monolith.

When to use one

Worth it when projects genuinely share code, when they are developed by the same people or team, when they release together, and when you want one place for linting, formatting and CI configuration.

Not worth it for a single application, for genuinely independent projects with different teams and release cycles, or when access control between projects must be strict.

For a student, the practical case is smaller but real: a project with a frontend, a backend and shared TypeScript types is a natural monorepo. Keeping the API's request and response types in a shared package, imported by both sides, means a change to the contract breaks the build immediately rather than at runtime.

That single benefit — one commit, both sides, types checked together — is worth demonstrating in a project, and it is a good thing to be able to explain in an interview.

Frequently Asked Questions

What is a monorepo? A single repository containing several projects with shared tooling and history. It is a repository layout choice, independent of whether you deploy as a monolith or as separate services.
Is a monorepo the same as a monolith? No. A monorepo is about where code is stored; a monolith is about how it is deployed. You can deploy many independent services from one monorepo.
Why do monorepos need special tooling? Without dependency-aware task running and caching, every change rebuilds and retests everything. Tools like Turborepo and Nx build only what is affected and reuse cached results.
What are workspaces? A package manager feature that installs dependencies once at the root and links local packages to each other, so a shared package resolves to the local folder rather than a published version.
Should a student project use a monorepo? It is worth it when you have a frontend and backend sharing types. Keeping the API contract in a shared package means a breaking change fails the build instead of failing at runtime.