Build, Buy, or Borrow — A Practical Decision Framework
- Shawn West
- Jan 8
- 12 min read
Updated: Aug 9
Every feature request is a decision in disguise: not "how do we build this?" but "should we build this at all?" The default answer is build — and it's usually the expensive one. Here's a five-question framework, a worked total-cost-of-ownership calculation you can copy, and a runnable test for each question so the choice is evidence, not instinct.
A four-person team — a composite of a dozen I've watched — decided to build their own background-job scheduler. The reasoning felt airtight in the room: "We need exactly-once execution, our workload is unusual, and the off-the-shelf schedulers don't quite fit." Three months went in. Then the incidents started — jobs firing twice on retry, a silent gap where a deploy dropped the in-flight queue, a midnight page because a worker died holding a lock nothing released. What they had actually shipped, after a quarter of engineering, was at-least-once execution with hand-rolled locking — precisely what every mature queue already gives you, minus a decade of other people's edge cases. The scheduler wasn't their product. It wasn't even visible to a customer. It was a commodity they'd rebuilt at full price, and now owned forever.
That is the shape of the most expensive decision in software, and it almost never announces itself. It doesn't feel like a mistake at decision time — building feels like engineering. The bill arrives later, as a maintenance line that never closes. This post is about catching the decision before it commits you.
Why the instinct is always to build
The pull toward build is worth naming, because you can't correct for a bias you pretend you don't have. Building is the most satisfying option on the table: you control the code, understand every line, and answer to no vendor's rate limit or pricing change. And building is sometimes right — the framework below exists to find those cases.
But building carries a cost developers chronically underweight: maintenance is not a phase, it's a permanent liability. Every line you write is a line you own until the system dies — tests, updates, security patches, and the quietly enormous one, being understood by whoever comes after you. When you build, you aren't spending the time to create the thing; you're signing up to keep it alive forever, including the onboarding tax on every future hire who must learn your custom scheduler before they can safely ship. The satisfaction is front-loaded; the cost amortizes across years you can't see from the whiteboard. It's the same deferred-cost trap behind architecture decisions you'll regret — the decision feels finished long before the bill is.
The alternatives trade that ownership away. Buy means paying a vendor whose whole business is keeping this system running — money and some control for reliability and offloaded maintenance. Borrow means using open source a community maintains — some risk (abandonment, breaking changes, a critical library kept alive by one burned-out person) for free, battle-tested code. Each is a different bet about where your risk sits. The framework is how you place it deliberately.
The framework: five questions, five runnable tests
For each capability you're considering, walk these five questions in order. Each one ends in a test you can run on your decision this week — a signal to check, a number to name, a document to cite. If a question's test fails, you have your answer before you write a line of code.
1. Is this your core differentiator? — the honesty test
If the capability is the reason your product exists — the unique value no competitor provides — build it. A payments company builds its payment engine; a search company builds its ranking. Everything that is not your differentiator is a candidate for buy or borrow, and most of what a team builds is not its differentiator. Authentication isn't. Email delivery isn't. User management, file storage, scheduling — unless your product is one of those, someone has already solved it better than your first try will.
The mechanism to watch is motivated: build is more interesting than integrate, so teams talk themselves into "everything is core" — and spend months on commodity plumbing instead of the thing that wins customers.
The test — name the customer-visible value. Write one sentence: "A customer chooses us over a competitor because we built this ourselves." For your scheduler, your auth, your email pipeline, that sentence is a lie and you'll feel it as you write it. If you can't say it with a straight face, the capability is not your differentiator — pass it to question 2. The scheduler team above never wrote that sentence. If they had, it would have read "customers choose us because our jobs run" — which is table stakes, not differentiation.
2. Does a mature product or library already exist? — the one-hour investigation
Before building, spend one real hour investigating — not a cursory search, an actual review. Read the docs. Check the commit activity and the issue tracker. Open the release notes. If a mature, actively maintained solution exists, the burden of proof flips onto building. You now need a specific, concrete, documented reason the existing option cannot work.
"We could build something better" is not that reason. It's a prediction, and almost always wrong: the existing solution has had years of users hitting edge cases you haven't imagined, and your from-scratch version has had none. "It doesn't handle requirement X" might be valid — but check first whether the product has an extension point or API, because building on top of a mature tool is usually a fraction of replacing it.
The test — the articulation check (borrowed from the architecture-regret piece because it's the sharpest version of this move). You may build only when you can state, with a documented specific, why the mature option fails. "It doesn't do exactly what we want" fails the test. "It cannot support data residency in these three jurisdictions, and here is the paragraph in its docs that confirms the limit" passes. If you can't cite the blocking line, you haven't finished evaluating — you've finished wanting to build. Run this test and most build cases collapse on contact.
3. What is the total cost of ownership? — do the math you keep skipping
This is where teams get the arithmetic wrong. The near-universal error is comparing the upfront cost of building (developer hours) to the upfront cost of buying (a subscription price) and concluding build is cheaper. It compares the wrong two numbers, because the real cost of build is the tail, not the head. So let's actually do the math instead of asserting it.
Here is a worked comparison you can copy into a spreadsheet. The scenario: a mid-size team weighing whether to build an internal notification/email-delivery service or subscribe to a mature provider, over a three-year ownership horizon. Every number below is illustrative — plug in your own loaded engineering cost and vendor quote.
(Illustrative calculation — substitute your real rates; these are placeholders, not researched figures.)
Assume a fully-loaded engineering cost of $100/hour (~$150k salary plus overhead, a common planning placeholder — use your actual number).
BUILD — three-year total cost of ownership
Initial development 320 hrs × $100 = $32,000
Testing & hardening 80 hrs × $100 = $8,000
Documentation 24 hrs × $100 = $2,400
Maintenance & security 120 hrs/yr × 3 × $100 = $36,000
Onboarding tax 8 hrs × 6 hires × $100 = $4,800
Deliverability/ops firefights (est.) = $12,000
------------------------------------------------------------
BUILD 3-YEAR TCO = $95,200
BUY — three-year total cost of ownership
Subscription $400/mo × 36 mo = $14,400
Integration (one-time) 40 hrs × $100 = $4,000
Working around vendor limits 16 hrs/yr × 3 × $100 = $4,800
Switching-cost reserve (clean-boundary re-integration, one-time) = $6,000
------------------------------------------------------------
BUY 3-YEAR TCO = $29,200
The build costs ~3.3× the buy over three years — and that's the optimistic build number, assuming maintenance hours don't grow and no security incident lands. The subscription line looks big and recurring; it's the small number here. The initial-development line looks like the whole cost; it's barely a third of it. The maintenance tail is where the money actually is, and it's exactly the line teams leave off the whiteboard. Two honest caveats keep this from being a rigged demo: the crossover does exist (a 40-hour build, or a $4,000/month subscription, inverts the result — which is why you run it instead of asserting it), and these are placeholders — the discipline is the line items, not my digits.
The test — build the two columns before you decide. If build's three-year total (initial + testing + docs + maintenance/yr × years + onboarding) doesn't beat buy's three-year total (subscription × months + integration + switching reserve) by a margin wide enough to survive your own optimism, buy. If you refuse to fill in the maintenance-per-year line at all, that refusal is the answer — you're hiding the cost that decides it.
4. What happens if this dependency disappears? — the reversibility test
Every buy or borrow introduces a dependency. The useful question isn't whether it's risky — it's how bad the vanishing would be and how hard the replacement is. That's a decision-type judgment: reversible choices and one-way doors demand different rigor.
A logging library — low risk, easily reversed. If abandoned, you swap it; the interface is standard. Just use it.
An authentication provider — medium risk, expensive to reverse. A real migration project, but the problem is well-understood and alternatives exist. Use it, but keep the boundary clean so you can move.
A one-maintainer library that is your core algorithm — high risk, structurally hard to reverse. Mitigation scales up: contribute, fork, or build in-house.
The mechanism: risk lives in the product of two things — how central the capability is and how few the exits are. A central capability with many alternatives is fine; a peripheral one with a single maintainer is fine. The danger is central and cornered.
The test — name the replacement and time it. For each dependency, write the specific alternative you'd switch to and estimate the migration in days. If you can name a replacement and it's a bounded project, the risk is priced in. If your honest answer is "there is no alternative and I don't know how long it would take," you've found a high-risk dependency that needs a mitigation now, not after it breaks.
5. How clean is the integration boundary? — the swap-cost test
Whatever you decide, the thing must connect to your system, and how it connects sets the cost of changing your mind later. A clean boundary means your app talks to the dependency through a thin interface — one wrapper module hiding the implementation. Swap the dependency, change one module. A dirty boundary means it's woven through everything: ORM-specific syntax in business logic, vendor API calls scattered across controllers, auth callbacks in your user model. Swap it, and you're editing fifty files.
This matters more for buy and borrow than for build, because you control less of how the dependency evolves — the vendor changes the API on their schedule, not yours. A clean seam turns "the vendor pivoted" from a rewrite into a weekend. It's also the discipline behind systems that survive their first 1,000 users: the parts most likely to change are the ones held at arm's length.
The test — count the files a swap would touch. Search your codebase for the dependency's name — the import, the vendor SDK symbol, the specific API call. If it appears behind one module, your boundary is clean. If it appears in fifteen files across three layers, your switching cost is real and you should either refactor to a seam now or fold that re-integration cost into the TCO in question 3 (it's the line I labeled "switching-cost reserve").
The discovery move: decide what you must learn first
Notice what every one of those five tests actually demands: a fact you don't have yet. The differentiator sentence, the cited blocking limitation, the maintenance-hours estimate, the named replacement, the file count — none come from the whiteboard. They come from discovery. The build instinct is so strong precisely because building lets you skip the discovery: you can start typing today, whereas naming the blocking limitation means reading a vendor's docs and admitting there isn't one. This is the ShiftQuality thesis in miniature — the failure isn't in the code, it's in the requirement no one validated first. The scheduler team's real mistake wasn't building; it was building before discovering that "exactly-once" is a promise no scheduler on earth keeps, theirs included. One hour of discovery would have surfaced the trap. This is why architecture matters before a single line of code: the cheapest place to catch a build-vs-buy mistake is the intake, not the retro.
A developed example: the "special" reporting engine
(Developed example — anonymized composite.)
Context. A ~60-person B2B SaaS company selling workflow software to operations teams. Customers kept asking for custom reports. Leadership decided reporting was a "strategic capability" and greenlit an in-house reporting engine — query builder, chart renderer, scheduled exports, the works.
The framework applied, after the fact. Run the five tests against the decision they actually made:
Differentiator? The honesty sentence — "customers choose us because we built our own charting" — was false. Customers chose them for the workflow automation. Reporting was table stakes. Fails question 1.
Mature option exists? An hour of the investigation they skipped would have found three embedded-analytics vendors and two open-source query-builder libraries, all handling the multi-tenant case they thought was special. They never wrote down a blocking limitation because there wasn't one. Fails question 2.
TCO? Their build ran ~500 hours to first release and settled into ~10 hours/week of maintenance as customers demanded new chart types — comfortably into six figures over two years. An embedded-analytics subscription for their seat count would have been a fraction of that. Fails question 3.
Reversibility? Because they built it, this one's inverted — they'd created a new dependency (on themselves) with no exit and a growing maintenance tail.
Boundary? The reporting code reached directly into the primary application database with tenant-specific query logic scattered across the codebase — a dirty boundary that made later extraction painful.
The result. Eighteen months in, the reporting engine was the single most-complained-about part of the product (customers compared it to real BI tools and found it thin) and the second-largest maintenance sink. They eventually adopted an embedded-analytics vendor, deleted most of the custom engine, and reassigned two engineers to the workflow features that were actually their differentiator.
The lesson. Every test would have failed before the first commit — and each failure was a discoverable fact, not a judgment call. The engine wasn't a bad build; it was a correct build of the wrong thing. The framework's job is to catch "wrong thing" while it's still an hour of investigation instead of eighteen months of regret.
The tradeoffs, in one table
Option | You gain | You pay | Best suited for | Warning sign |
Build | Total control, exact fit, no license fees | Permanent maintenance + security surface + onboarding tax | Your actual differentiator, or a cited requirement with no market answer | You can't name the customer-visible value, or can't cite the blocking limitation |
Buy | Reliability, offloaded maintenance, speed | Subscription cost, vendor roadmap, lock-in | Mature, mission-critical commodities (auth, payments, email) | No clean boundary; a switch would touch fifty files |
Borrow | Free, community-hardened code | Abandonment risk, breaking changes, upgrade tax | Well-maintained libraries with real alternatives | One-maintainer dependency on a core capability |
Every row is the same shape as the scheduler: with build, the cost is certain and paid now-and-forever; with buy and borrow, the cost is a bounded fee plus a switching risk you can cap with a clean seam.
When building the "commodity" thing is actually right
The framework's default is buy-or-borrow for anything non-core — but defaults have documented exceptions, and skipping them earns the opposite regret:
The capability is genuinely your differentiator (question 1 protects this).
You can cite a real, verified blocking limitation — not "doesn't quite fit," but a documented gap you can point to in the vendor's own docs (question 2's articulation test made rigorous).
The dependency is central and cornered — when the only maintained option is one person's side project on your critical path, a bounded in-house version can beat the risk (question 4 surfaces this).
The TCO genuinely inverts — a trivial 40-hour build against an expensive subscription can favor build, but only if question 3's full three-year columns say so, not your gut.
The through-line: build custom only where you can point to the specific fact that earns it. "It felt cleaner" is not a fact.
Final takeaway
The most productive teams aren't the ones that write the most code — they write the right code and integrate everything else, and the discipline that gets them there is refusing to let "build" be the default answer to a question no one asked out loud. So change what you do this week: take the one capability your team is arguing about building, and run the five tests in order — write the differentiator sentence, spend the one hour, fill in both TCO columns with real hours, name the replacement, count the files. Each test hands you a fact. If build survives all five with facts on its side, build with confidence — you've earned it. If it fails even one, you just saved yourself a scheduler you'd have owned forever. The decision not to build is still a decision; make it the one your evidence supports, not the one your instinct prefers.
Related on ShiftQuality: why the structural call comes first (Why Architecture Matters Before a Single Line of Code); the five structural bets teams regret (Architecture Decisions You'll Regret); weighting reversibility and evidence (Technical Decision-Making Under Uncertainty); and building only for the load you have (Designing Systems That Survive Your First 1,000 Users).
Sources. The total-cost-of-ownership figures above are labeled illustrative placeholders, not researched benchmarks — substitute your own loaded engineering rate and vendor quote before relying on them. The developed example is an anonymized composite, not a documented case study.


