How Monorepos Boost Team Productivity

An exploration of how monorepo architecture improves developer velocity, code quality, and cross-team collaboration, based on real-world experience with Andromeda.

business9 min readBy Klivvr Engineering
Share:

The monorepo versus polyrepo debate has been raging in the software industry for over a decade. Google, Meta, and Microsoft famously use monorepos for massive codebases. Meanwhile, many organizations default to one repository per service, treating it as an obvious best practice for microservice architectures. At Klivvr, we started Andromeda with separate repositories for each service and migrated to a monorepo after nine months. The productivity improvements were immediate and sustained. This article explains why.

This is not a technical deep dive into tooling or build systems. Those topics are covered elsewhere in our engineering articles. Instead, this piece focuses on the human factors: how a monorepo changes the way teams work, communicate, and ship software.

The Hidden Cost of Polyrepos

Before explaining the benefits of a monorepo, it is worth examining what we lost in the polyrepo setup. The costs were not dramatic. No single issue was a crisis. But the cumulative friction was substantial.

Cross-service changes required choreography. When a change to a shared library affected three services, the workflow was: update the library, tag a new version, open pull requests in each service to bump the dependency, wait for CI in each repo, and merge in the right order. A change that took thirty minutes to write could take a full day to land.

Discovery was fragmented. Engineers working on the payments service had no easy way to see how the accounts service handled a similar problem. They could browse the other repo, but it required context-switching: opening a different repository, finding the relevant code, and mentally mapping a different project structure. In practice, most engineers did not bother. The result was duplicated logic and inconsistent approaches across services.

Onboarding was slow. A new engineer had to clone multiple repositories, set up each one's development environment, understand each one's CI configuration, and learn each one's conventions. Even small differences, like one repo using make test and another using go test ./..., added cognitive load.

Refactoring was avoided. Because cross-repo refactors were painful, engineers worked around stale abstractions rather than improving them. Technical debt accumulated not because engineers were lazy but because the repository structure made the right thing disproportionately expensive.

These costs are familiar to anyone who has worked in a polyrepo microservice architecture. They are not unique to Andromeda or to Go. They are structural consequences of fragmenting a cohesive system across multiple repositories.

Atomic Changes Across Services

The most immediate productivity gain from the monorepo was the ability to make atomic changes. A pull request that modifies a shared protobuf definition, updates the generated code, and adjusts every service that uses the definition is a single commit, a single code review, and a single CI run.

Consider the real-world example of adding a new field to the Account protobuf message. In the polyrepo world, this required changes in the proto repository, the accounts service, the gateway service, and any other service that consumed account data. In the monorepo, it is one pull request:

The diff touches four files across three directories. The reviewer sees the entire change in context. CI validates that everything compiles and all tests pass. The merge is atomic: there is no window where the proto definition is updated but a consuming service is not.

This atomic change capability does not just save time. It changes behavior. Engineers are more willing to make cross-cutting improvements because the cost is proportional to the size of the change, not the number of repositories it touches.

Code Discovery and Knowledge Sharing

In a monorepo, every engineer has access to every service's code without any additional setup. This has profound effects on knowledge sharing.

When an engineer working on the notifications service needs to understand how the payments service validates currency codes, they search the monorepo. The code is right there, using the same conventions, the same directory structure, and the same patterns. There is no context switch, no separate clone, no unfamiliar project layout.

We have observed that engineers in a monorepo are significantly more likely to read code from other services. This cross-pollination has tangible benefits. It reduces duplication because engineers discover existing solutions before writing new ones. It improves consistency because engineers adopt patterns they see in other services. And it builds shared understanding of the system, which is invaluable during incident response when anyone might need to debug any service.

We reinforced this benefit by establishing consistent patterns across all services. Every service follows the same directory layout (domain, app, infra, ports). Every service uses the same error handling conventions. Every service structures its tests the same way. This consistency means that familiarity with one service transfers immediately to every other service.

Simplified Code Review

Code review is a bottleneck in most engineering organizations. In a polyrepo setup, it is worse because reviews are fragmented. A cross-cutting change produces multiple pull requests, each reviewed in isolation. Reviewers see part of the picture and must mentally reconstruct the whole.

In the monorepo, a cross-cutting change is a single pull request. The reviewer sees the entire change, including the motivation (the shared library update), the implementation (the service changes), and the validation (the test updates). This comprehensive view leads to higher-quality reviews because the reviewer can assess the change holistically.

We also found that monorepo code reviews are more likely to produce useful feedback about patterns and consistency. When a reviewer sees a new service implementing a pattern differently from existing services, they can point to the existing implementation and suggest alignment. In a polyrepo, the reviewer might not even know how other services handle the same problem.

Consistent Tooling and Standards

In a monorepo, tooling decisions are made once and applied everywhere. The linter configuration, the CI pipeline, the Makefile targets, the code generation scripts, and the testing conventions are defined at the repository root and inherited by every service.

This consistency has two benefits. First, it reduces the maintenance burden. Instead of maintaining fifteen CI configurations, we maintain one. Instead of fifteen linter configs, one. When we upgrade the Go version or update a linter rule, the change is applied universally in a single commit.

Second, it ensures that standards are actually enforced. In a polyrepo setup, it is easy for individual repositories to drift from organizational standards. A team might disable a linter rule that annoys them or skip a CI step to save time. In a monorepo, standards are encoded in shared configuration that applies to everyone. Deviations require modifying a shared file, which is visible to and reviewable by the entire team.

Faster Onboarding

New engineers at Klivvr clone one repository, run one setup command, and have the entire Andromeda platform on their machine. They can build any service, run any test suite, and read any code. The development environment is consistent because it is defined once in the repository root.

We measured onboarding time before and after the monorepo migration. Before, it took a new engineer an average of three days to have a fully working development environment and submit their first pull request. After, it took one day. The time saved was not from faster tooling but from eliminated confusion: there was exactly one way to set things up, one place to look for documentation, and one set of conventions to learn.

The Trade-offs

Intellectual honesty requires acknowledging the trade-offs. A monorepo is not universally better.

CI complexity. A naive CI pipeline that builds and tests everything on every commit does not scale. We invested in affected-service detection, parallel builds, and aggressive caching. This is real engineering work that a polyrepo setup does not require.

Repository size. Over time, the repository grows large. Git operations slow down without optimizations like shallow clones, sparse checkouts, or Git's built-in features for large repositories. We have not hit this limit yet, but it is on our radar.

Team autonomy. In a polyrepo, each team owns their repository and can make independent decisions about tooling, dependencies, and conventions. In a monorepo, these decisions are shared. Teams that value autonomy highly may find the monorepo constraining. We mitigate this by involving all teams in decisions that affect shared configuration and by allowing service-specific overrides where they genuinely make sense.

Access control. Some organizations need fine-grained access control at the repository level. A monorepo makes this harder because everyone has access to everything. For Andromeda, this is a feature, not a bug, because we want cross-team visibility. For organizations with strict regulatory requirements around code access, it could be a genuine issue.

Measuring the Impact

We track several metrics that reflect the productivity impact of our monorepo:

  • Lead time (time from first commit to production deployment): decreased by 40% after the migration, primarily because cross-service changes no longer required coordinated releases.
  • Pull request cycle time (time from PR open to merge): decreased by 25%, driven by simpler reviews and fewer coordination delays.
  • Cross-service contributions (percentage of PRs that touch multiple services): increased from 8% to 22%, indicating that engineers are more willing to make cross-cutting improvements.
  • Duplication rate (measured by static analysis): decreased by 30% over the first six months, as engineers discovered and reused existing shared code.

These numbers are specific to Andromeda and may not generalize to every organization. But they are consistent with the experience reported by other companies that have adopted monorepos.

Conclusion

A monorepo is not a silver bullet. It introduces its own complexities, particularly around CI, repository size, and shared decision-making. But for a team building a cohesive product with tightly integrated services, the productivity benefits are substantial. Atomic cross-service changes, effortless code discovery, simplified code review, consistent tooling, and faster onboarding compound over time, making the entire engineering organization more effective.

The decision to adopt a monorepo should be driven by the team's actual pain points. If cross-service changes are rare and each service is maintained by a completely independent team, a polyrepo may be fine. But if your services share types, communicate frequently, and are built by a product-focused team, the monorepo's benefits will likely outweigh its costs. Our experience with Andromeda strongly supports that conclusion.

Related Articles

technical

Testing Strategies for Go Backend Services

A comprehensive guide to testing Go backend services, covering unit tests, integration tests, end-to-end tests, table-driven patterns, test fixtures, and strategies for testing gRPC and NATS-based systems.

11 min read
technical

Observability in Go: Tracing, Metrics, and Logging

A practical guide to implementing observability in Go backend services using OpenTelemetry for tracing, Prometheus for metrics, and structured logging with log/slog.

7 min read
business

Scaling Go Services: From Startup to Enterprise

A business-oriented guide to scaling Go backend services, covering horizontal scaling strategies, performance optimization, and the organizational practices that enable sustainable growth.

7 min read