The Framework You Chose Is Solving Google's Problems, Not Yours
Tooling built to coordinate thousands of engineers rarely helps three of them

Contents
Every framework, orchestration platform and architectural pattern that dominates hiring pages was built to solve a specific, named problem at a specific, named company, at a specific scale. React exists because Facebook had thousands of engineers touching a single sprawling UI codebase and needed a way to reason about state changes without the whole thing collapsing into spaghetti. Kubernetes exists because Google had tens of thousands of machines and needed to schedule workloads across them without a human making the placement decision. Microservices exist because Amazon had hundreds of teams that needed to ship independently without waiting on each other’s release trains.
Those are real problems, solved well, by tools that earn their complexity at the scale they were built for. The trouble starts when a three-person startup adopts the tool without inheriting the problem, and ends up carrying the coordination overhead of an organisation twenty times its size, for a codebase one engineer could hold in their head.
The scale mismatch, named precisely
The pattern is consistent enough to state as a rule: a tool’s complexity is a direct reflection of the coordination problem it was designed to solve, and adopting the tool without the problem means paying the coordination cost with nothing to coordinate.
Kubernetes was built to answer “given ten thousand machines and constantly shifting workloads, which machine should run which container, and what happens automatically when one dies.” A three-person team running four services can name, from memory, which machine each one runs on, which is the entire machine-placement problem already solved without a scheduler. The scheduler, the etcd cluster backing it, the networking layer stitching pod-to-pod traffic together, and the ongoing patching burden of the control plane itself are all solving a problem this team does not have, and Kubernetes is a very expensive while true loop is precisely that reconciliation engine, running continuously, for four services that would fit on one machine with a single systemd unit each.
Microservices were built to answer “given hundreds of teams that cannot coordinate a shared release schedule, how do we let each one ship independently without a change in one system breaking another team’s system on a different timeline.” A team of six engineers sharing one codebase already ships together, reviews each other’s changes in the same repository, and can coordinate a breaking change over a five-minute conversation instead of a versioned API contract. Splitting that codebase into nine services does not remove coordination — it relocates it from a pull request into a distributed system, where the same conversation now needs a network call, a retry policy and a fallback for when the other service is down. Microservices are an org-chart problem wearing a tech costume covers exactly this: the architecture exists to route around a communication problem between teams, and a team small enough to talk in one room does not have that problem to route around.
React itself is a more interesting case, because unlike Kubernetes or a microservice split, it is rarely wrong to use — but the ecosystem built around it, assuming Facebook-scale component counts and Facebook-scale build tooling investment, frequently is. A five-page marketing site does not need a client-side router, a state management library and a build pipeline with code-splitting tuned for a component tree with thousands of nodes. It needs a static site, which is the fastest website you will ever build, and the honest test for whether React earns its place is whether the page has enough genuinely interactive, stateful UI to justify the tooling — not whether React is what teams at the scale you aspire to are using.
Why the mismatch keeps happening
Nobody adopts these tools by running the arithmetic and getting the wrong answer. They adopt them because the tool is what serious engineering looks like from the outside, and looking serious is a real, if quiet, motivation in technical decision-making that rarely gets said out loud in the meeting where the decision gets made.
There is a genuine asymmetry in how these decisions get evaluated. Adopting Kubernetes for four services is rarely challenged, because it is the industry-standard choice and nobody wants to be the person arguing against the standard. Choosing a single systemd unit and a cron job is the decision that gets questioned in the design review, even when it is the one that actually fits the problem, because it looks like the less sophisticated choice rather than the more precisely fitted one.
Hiring plays a role too. Job postings list Kubernetes, React and a service-mesh architecture because those are the searchable, recognisable keywords candidates filter on, and a team that wants to attract engineers who know those tools has a real incentive to use them regardless of fit. That incentive is legitimate — nobody is choosing badly on purpose — but it is a hiring decision wearing an architecture decision’s clothes, and it is worth naming which one is actually driving the choice.
What the mismatch actually costs
The cost is not abstract. A small team running Kubernetes for a handful of services spends real engineering hours on cluster upgrades, CNI plugin behaviour, and etcd backup strategy — hours that produce zero customer-facing value and exist purely to keep the coordination machinery itself running. A small team split into nine microservices spends real hours on distributed tracing to reconstruct what a single stack trace used to show for free, on service-to-service authentication for calls that used to be function calls, and on the eventual-consistency bugs that a shared database transaction used to make structurally impossible.
| Tool | Problem it solves | Cost paid without that problem |
|---|---|---|
| Kubernetes | Scheduling across thousands of machines | Control-plane upkeep, CNI debugging, upgrade cadence |
| Microservices | Independent shipping across hundreds of teams | Network calls replacing function calls, distributed tracing, eventual consistency |
| Heavy SPA tooling | Thousands of interactive components | Build pipeline maintenance, hydration bugs, bundle-size chasing |
| Distributed data platform | Datasets exceeding one machine’s RAM and disk | Cluster operations for a workload one server would handle |
None of these costs are hypothetical or exaggerated for effect — they are the ordinary, documented experience of running each tool, paid whether or not the workload justifies it.
The test that actually distinguishes fit from cargo-culting
The honest question is never “what do the biggest companies use.” It is “what problem, specifically, does this tool solve, and do I have that problem, measured, right now.”
For Kubernetes: do you have more machines than one engineer can track by name, or workloads that genuinely need to be rescheduled dynamically across a fleet rather than restarted on the box they already run on? For microservices: do you have enough independent teams that a shared release schedule is the actual bottleneck, rather than a codebase small enough that everyone already reviews everyone else’s changes? For a heavy frontend framework: does the page have enough real, stateful interactivity that server-rendered HTML and a sprinkle of vanilla JavaScript would genuinely fall short?
If the honest answer to any of these is “not yet,” the tool is solving a problem that belongs to a company you are not currently running. The fit will improve on its own the day the team, the machine count, or the component tree actually grows into it, and that future date is exactly when it deserves reconsideration, rather than a reason to pay its cost today.
The counter-argument, taken seriously
The strongest objection to all of this is that adopting the bigger tool early avoids a painful migration later, and that objection deserves a real answer rather than a dismissal, because sometimes it is correct. A team that already knows, with real confidence, that it will hire fifty engineers within eighteen months has a legitimate case for structuring the codebase with future team boundaries in mind, and a team whose product genuinely requires a rich, stateful, thousands-of-components interface from day one is not cargo-culting by reaching for a serious frontend framework.
The honest test for that objection is whether the future scale is a forecast the business has actually committed to — funded headcount, a signed contract requiring a specific throughput, a board-approved growth plan — or whether it is aspiration dressed as a plan. Migrating a well-structured monolith into services later, once team boundaries are real, is a known, bounded, mechanical piece of work: extract a module, give it its own datastore, put a network interface where a function call used to be. Migrating away from a premature microservice split, unwinding distributed transactions and reconstructing a shared data model split across nine independent stores, is the harder direction to travel and the one teams rarely budget time for, because nobody plans a project called “put the architecture back together.”
That asymmetry is the actual argument for starting simple: the path from monolith to services is well-trodden and mechanical, while the path from a premature split back to something a small team can operate is the one that ends up costing a quarter nobody scheduled for it.
Troubleshooting: auditing an architecture you already adopted
If this describes infrastructure already running rather than a decision still on the table, the audit is straightforward. Count the machines a Kubernetes cluster actually schedules across, and compare that to the number one engineer could name from memory — if those numbers are close, the scheduler is solving a problem the team solves faster in their own head. Count the services in a microservice architecture against the number of teams that ship independently; if one team owns all nine services, the split is organisational theatre rather than an organisational boundary. Check whether a frontend’s bundle size and build time are being spent on interactivity the page actually has, by opening the component tree and counting how many components hold genuine client-side state versus how many are static structure that a template could have rendered.
None of these audits require a rewrite to run. They require an honest hour with the existing system and the willingness to accept that the answer might be “we should simplify this,” which is a harder sentence to say in a planning meeting than “we should adopt the standard tool,” even when it is the truer one.
What the right-sized alternative actually looks like
None of this is an argument for doing without tooling. It is an argument for choosing tooling scaled to the actual problem, and the right-sized alternative is usually more capable than its reputation suggests, precisely because so few teams ever push it hard enough to find its edges.
A single well-specified server running a handful of systemd units, each restarted automatically on failure and logged through the journal, handles the availability requirements of the overwhelming majority of small services without a scheduler, a control plane, or a networking overlay. Docker Compose, on that same single server, gives most of what a small team actually wants from Kubernetes — declarative service definitions, restart policies, isolated networking between containers — without a cluster to operate. A monolith with clean internal module boundaries gets almost all the maintainability benefit usually credited to microservices, because the discipline that makes a service boundary useful is the same discipline that makes a well-organised module useful, and the module version does not need a network call to enforce it. Server-rendered pages with a light sprinkle of vanilla JavaScript, or a framework used only where a page genuinely needs client-side state, deliver a fast, maintainable site without a build pipeline sized for a component count the site will never reach.
Each of these is a smaller, more boring choice, and each is also a completely legitimate, well-understood piece of engineering rather than a placeholder for the “real” architecture waiting further down the road. The team that chooses them keeps shipping features on the same afternoon the other team spends reading etcd documentation to understand why the cluster stopped scheduling pods.
Is matching the tool to the company ever worth the discomfort
Yes, and the discomfort is smaller than the alternative. Choosing the boring, right-sized tool over the impressive, oversized one costs a moment of feeling less cutting-edge in a planning meeting. Choosing the oversized tool costs months of engineering time spent operating infrastructure built for a problem the team does not have, on a codebase that would have shipped faster, broken less, and onboarded new engineers quicker running on the tool actually matched to its size. Match the architecture to the organisation you have today, and let the measured numbers earn the next tool when the time genuinely comes.




