I inherited a React Native codebase recently. The code is good. Patterns are consistent. Clear separation of concerns. It was obviously built by professionals who cared about doing things right.
And yet — it slows me down constantly.
How can well-built code be technical debt?
What I Inherited
The architecture follows best practices for a large team:
-
Multiple repositories: API backend, shared types and HTTP layer, separate client apps
-
TypeScript throughout with dependency injection
-
Clean separation: screens call useCases, useCases call repositories, repositories hit API endpoints
-
Shared type definitions so all clients stay in sync
This was built by a team that varied between 8 and 16 developers over two years. At that scale, the architecture makes sense. Clear boundaries let multiple people work without stepping on each other. Formal contracts between layers mean teams can develop in parallel.
Then the team shrank to 3 developers. I’m one of them.
The Symptoms
The 4-hop trace
I need to know what endpoint supports a feature. So I find the screen, find the useCase it imports from the shared package, go to the shared project to find that useCase, find the repository it wraps, and finally — the URI.
Four hops across two repositories to answer "what URL does this button call?"
What was separation of concerns becomes archaeology.
TypeScript theater
The types exist. But when they’re inconvenient, they get bypassed.
Liberal use of any. @ts-ignore scattered through the codebase. Types
from the shared package reimplemented locally in each client because of
"some issues" with the shared versions.
So the shared type repository — the single source of truth that justified this whole structure — gets duplicated into every client anyway. You pay the tooling overhead without the safety guarantees.
The multiplication problem
Adding a new type means touching four repositories. What was parallel work for four teams becomes sequential work for one person.
The coordination overhead designed for 16 developers doesn’t disappear when you’re down to 3. It just falls on fewer shoulders.
Testing gridlock
The shared package uses dependency injection, which should make testing easy. Mock the useCase, test the screen in isolation.
But the useCases come through a React context. The context uses barrel imports. Barrel imports pull in everything. And somewhere in that everything is a React Native module that Jest doesn’t support.
So mocking one useCase means fighting the entire dependency graph. The "clean" architecture creates implicit coupling through the module system. I can’t isolate what I want to test.
The Reframe
Here’s what I’ve realized: the original team wasn’t wrong.
At 16 developers, you need repo boundaries. Otherwise you’re constantly resolving merge conflicts in shared code. You need abstraction layers so teams can work independently. You need formal contracts between components.
Those patterns weren’t over-engineering. They were engineering for the actual constraints of a 16-person team working in parallel.
But architecture doesn’t scale down automatically.
The coordination overhead that enables 16 people to work without colliding becomes pure friction for 3 people who are never going to collide anyway. The abstraction layers that let teams work independently just hide where things are when one person needs to trace a bug. The multi-repo structure that prevented merge conflicts means I’m the only one in each repo, pushing and pulling between them.
The original builders weren’t wrong. They built for their context. The mistake is assuming that context transfers.
What To Look For
When you’re inheriting a codebase, ask: how big was the team that built this?
If the answer is 3-4x your current size, expect patterns that were solutions to become problems. Look for:
-
Abstraction layers that hide rather than clarify
-
Coordination mechanisms with no one left to coordinate
-
Type systems that get bypassed because they’re friction without payoff
-
Test infrastructure that assumes isolation you can’t achieve
The code isn’t bad. It’s just not yours yet.