Build, buy, or generate

  • engineering-practice
  • dependencies
  • build-vs-buy
  • architecture

For as long as software has been assembled rather than written from nothing, one of the most consequential decisions a team makes is whether to build a capability or to take it from outside — a library, a package, a service, an open-source component someone else wrote and maintains. The decision had a settled shape and a settled rule. Building cost expensive engineering time; adopting a dependency cost almost none. So you built the things that were core to what you did, the things that differentiated you, and you took everything else off the shelf. Nobody wrote their own date library, their own retry logic, their own cryptographic primitives, not because they couldn’t, but because the engineering time it would cost was indefensible against a mature dependency that already existed. The cost of building was the thing that kept the rule enforced.

But the decision was never really build versus buy. That framing names the visible act and hides the actual trade. What a team is choosing between is owning a capability and renting one. When you take a dependency, you are renting someone else’s ongoing maintenance of it — their bug fixes, their security patches, their slow accumulation of fixes for edge cases you will never encounter and never think of. When you build, you own all of that, forever, yourself. The price of building was high because building is how you come to own something, and owning is expensive. The build cost was a proxy. “Too expensive to build” was a rough but reliable signal that the thing was also expensive to own, and the signal pushed teams toward renting whenever a credible rental existed.

AI tools cut the cost of acquiring code without cutting the cost of owning it. Generate it is build it with the up-front price driven close to zero — the capability appears in an afternoon instead of a quarter. And because the up-front price was the part everyone could see, generating feels like getting the dependency for free. It is not. It is choosing to own — for the entire life of the system — something you could have rented, and doing so at the precise moment the signal that used to warn you against owning has gone silent. The acquisition got cheap. The ownership did not, and the proxy that used to price the ownership broke.

Three things a team gives up when it generates what it could have rented. None of them shows up the day the code is generated, which is exactly why the choice feels free at the moment it is made.

1. The maintenance you stop renting

The value of a mature dependency was never the code in it. The code is the part you could always have written. The value is everything that accreted around the code after it shipped: the bug filed by a user on a platform you have never deployed to, the security patch issued the week a vulnerability was disclosed, the fix for the malformed input that only one caller in ten thousand ever sends. A widely-used dependency is hardened by the collective experience of everyone who uses it, and that hardening arrives at your door continuously, without your involvement, because you are renting it. You inherit the lessons of a population you are not part of.

Generated code arrives with none of that. It is correct for the cases the team thought to ask about and tested against, and it has been exercised by exactly one user: the team that generated it. It looks like the dependency — it has the same surface, the same function names, it passes the same obvious tests — but it is missing the part of the dependency that was never visible in the source: the years of contact with inputs nobody anticipated. The generated version is the library as it would have been on the day it was first published, before anyone had used it in anger, frozen at its most naive.

Call this leaving the commons. A dependency is a shared asset whose hardening is paid for by its whole user base and drawn down by each member for free. Generating the equivalent removes the team from that commons. It now hits, alone, every edge case the dependency’s population had already absorbed on everyone’s behalf, and it discovers each one the expensive way — in production, as a defect, rather than for free, as a patch someone else already wrote. The code looked identical. The thing that made the dependency worth having was not in the code.

The remedy is to price a dependency by its maintenance stream and not by its surface. Before generating an equivalent, the question is not can we produce this — the answer is now yes, cheaply — but what hardening are we walking away from, and are we prepared to reproduce it ourselves, incident by incident. For a genuinely trivial capability the answer is fine; there was no commons to leave. For anything that has earned its maturity through long exposure — anything to do with security, protocols, parsing, time, money, the failure modes of networks — the commons is most of the value, and leaving it is most of the cost. Teams that ask what they are giving up keep renting the things whose value was the rental. Teams that price only the surface generate a hollow copy and meet the missing hardening one outage at a time.

2. The dependency that stops aging

A maintained dependency does something a snapshot of its code cannot: it tracks a moving world. The runtime it runs on releases new versions; the protocol it speaks gets revised; a vulnerability is discovered in an approach everyone believed was sound; a platform deprecates the call it relied on. A live dependency absorbs all of this on your behalf, because its maintainers are keeping it current against the same shifting ground you are standing on. Currency is part of what you rent. You are not just renting the code as it is; you are renting its continued agreement with a world that does not hold still.

Generated code is frozen at the instant of generation. It is a snapshot, and it ages the way a snapshot ages — not gracefully, with the world, but against it. It is correct on the day it is produced and becomes progressively less correct as the ground underneath it shifts, and nobody is keeping it aligned because the team that generated it has long since moved on to other work. The danger is that the decay is invisible for a long time. The code still runs. The tests still pass. There is no error, because nothing has broken yet — and then the runtime drops the old behavior, or the vulnerability is found, or the protocol revision lands, and a capability the team had stopped thinking about turns out to have been quietly rotting since the afternoon it was generated.

Call this the frozen dependency. Generating an equivalent converts a living thing — a dependency that ages with the world because someone is paid to keep it there — into a dead one that ages against the world because no one is. The conversion is the whole point of why renting existed, and it is precisely the property that does not appear in a comparison of the two on the day you choose between them. On that day, the generated version and the dependency are indistinguishable. They diverge later, in one direction only.

The remedy is to treat every generated capability as a maintenance commitment with no maintainer yet assigned, and to refuse to generate it until someone is. A dependency comes with its upkeep included; a generated equivalent comes with the upkeep deleted from the invoice but not from reality. If the team is not willing to name who keeps this code current against its moving world — to schedule the attention a maintainer would have spent — then it is not choosing to build the capability, it is choosing to abandon it at birth and discover the abandonment later. Teams that assign the ownership keep the generated code alive. Teams that generate and walk away are accruing a stale dependency that will present its bill on a schedule they do not control.

3. The criteria inverted

The old rule had a clean division: build the things that are core to what you do and differentiate you; rent everything else, because everything else is commodity, and spending expensive engineering time to own a commodity is the definition of waste. The build cost enforced the division automatically. A team could not casually build its own commodity infrastructure, because the time it would take was self-evidently better spent elsewhere. The expense did the team’s prioritization for it.

Cheap generation removes the enforcement, and removing it inverts the result. The commodity capabilities — authentication, retries, date and timezone handling, parsing, the well-trodden plumbing every system needs — are now the cheapest things to generate, because they are the most standard and the most thoroughly represented in everything the model learned from. So that is what teams reach to generate first: the easy, standard, undifferentiated plumbing. And the commodity is the worst possible thing to own. It is precisely the category with the deepest accumulated hardening in its mature dependency form, and the most continuous exposure to the moving world — the place where both of the first two costs land hardest. The team is drawn to generate exactly the capabilities whose rental was the best deal it had.

Meanwhile the differentiating core — the thing genuinely specific to what the team does — is where generating is most defensible, and it is the place teams hesitate, because it feels important enough to build by hand. But the core is the thing for which no commons exists, no mature dependency is available to rent, and the team was always going to own the maintenance regardless. There is nothing to leave and nothing to age against that the team was not already on the hook for. Generation fits the core and misfits the commodity, and the cost gradient pulls every team toward doing the reverse.

Call this generating the commodity: the cheapness of generating standard capabilities steers teams to own the things they should rent and to agonize over the things they could safely generate. The remedy is to keep the old rule and invert the team’s suspicion to match the new cost structure. The cheaper and more standard a capability is to generate, the harder the team should look for the mature dependency that already owns its hardened, world-tracking version — because cheap-to-generate is now a reliable signal of commodity, and commodity is a reliable signal that renting wins. The expense used to make this judgment for free. It no longer does, so the judgment has to be made on purpose, against the gradient. Teams that invert their suspicion rent the commodity and generate the core. Teams that follow the cost gradient generate the commodity, hand-build the core out of residual caution, and get the decision backwards in both directions at once.

Closing

Build-versus-buy was always own-versus-rent wearing a more concrete name, and the price of building was a proxy for the price of owning — a crude gauge, but one that reliably steered teams toward renting the maintenance of anything whose maintenance was worth renting. AI broke the gauge. It cut the cost of acquiring a capability to nearly nothing while leaving the cost of owning one exactly where it was, and it disguised the difference by making the expensive choice look like the free one. The team that generates a dependency has not saved the cost of buying it. It has hidden the cost of owning it, on a surface that will not render the bill for a year.

This is not an argument against the generate option. The option is real and frequently right: when no commons exists to leave, when the capability is genuinely the team’s own, when the ground it sits on is not moving, generating is build-versus-buy finally resolving in the build direction at a price that used to be prohibitive — a clear gain. The error is not generating. The error is generating without noticing that the choice was never about how to acquire the code. It was about who maintains it, against a changing world, for the rest of the system’s life — and that question costs precisely what it always did, no matter how cheap the code became to produce.

The question worth asking is no longer can we build this ourselves, because the answer is now always yes, and the answer carries no information. The question is do we want to own this until it dies — and the team that generates a capability has answered yes to that question whether it meant to or not. The cheapness of the generating is what makes the answer so easy to give without hearing it.