Skip to main content

Build vs. Buy: Own the Layers Where the Business Changes

Build versus buy is usually presented as a comparison between implementation costs.

How much time would it take us to build this?

How much does the product cost?

How many engineers would we need?

How quickly could we get something into production?

Those questions matter, but they begin the analysis too late. Before comparing prices, timelines, vendors, or engineering estimates, there is a more important question:

Where does the complexity live?

The answer determines which parts of a system should be owned internally and which parts should be delegated to an existing product, service, library, or platform.

My general rule is:

Build where requirements are business-specific, frequently changing, and central to the value of the system. Buy where requirements are stable, well-understood, and largely independent of the business.

This is not a preference for custom software over commercial software. It is not a preference for bare metal over managed services. It is a way of deciding which abstractions are useful and which ones are likely to become constraints.

Buying Does Not Eliminate Complexity

One of the most misleading assumptions in a build-versus-buy discussion is that buying necessarily requires less work.

Sometimes it does.

A good managed service can remove years of engineering and operational responsibility. In other cases, buying merely changes the form of the work.

Instead of writing code, the team spends its time:

  • evaluating vendors;
  • learning a proprietary platform;
  • configuring workflows;
  • translating business requirements into the platform's model;
  • debugging undocumented behavior;
  • writing adapters around platform limitations;
  • requesting features from the vendor;
  • and creating workarounds when the required behavior cannot be represented directly.

The amount of code written is not a reliable measurement of system complexity.

A system with 500 lines of straightforward application code may be substantially easier to understand and operate than a platform containing dozens of workflows, configuration rules, hidden defaults, and vendor-specific conventions.

Buying can remove complexity, but it can also conceal complexity behind an abstraction.

The important question is not whether a vendor wrote the code. The important question is whether the vendor's abstraction matches the problem.

Buy Stable, Non-Differentiating Capabilities

Authentication is a good example of something that most applications should buy rather than build.

An application may need:

  • password storage;
  • password resets;
  • email verification;
  • multifactor authentication;
  • social login;
  • session management;
  • account recovery;
  • device tracking;
  • token rotation;
  • abuse protection;
  • and security updates.

These capabilities are important, but they are rarely the reason the application exists.

For one of my applications, I use Clerk. I do not want to build and operate every surrounding authentication workflow myself. Those features are well understood, broadly shared across applications, security-sensitive, and largely independent of my product's business logic.

Clerk removes a category of responsibility without constraining the parts of the application where the product actually differentiates.

That is an excellent use of an external service.

The same principle applies at lower levels of the stack.

If Kafka fits the system's requirements, there is little reason to recreate a distributed append-only log, replication model, partitioning system, consumer coordination mechanism, and high-throughput data path. Kafka exists because implementing those properties correctly and operating them at scale is difficult.

Its internals can take advantage of properties such as sequential writes and zero-copy data transfer. Most application teams do not create business value by recreating those mechanisms.

Use Kafka, and write the event-processing logic that belongs to the business.

Similarly, Docker provides container packaging and process isolation primitives. The application remains under the team's control. Engineers can define the image, inspect the filesystem, execute commands inside the container, configure its runtime, and deploy it through many different environments.

Docker provides a useful primitive without attempting to express the application's business rules.

That is the pattern to look for: a tool should remove incidental complexity while preserving control over the logic that matters.

Build Where the Logic Is the Product

The opposite case occurs when the difficult part of the system is the business-specific logic itself.

On one client project, we needed to synchronize data between external systems. At first glance, this appeared to be a standard integration problem, which made existing synchronization and change-data-capture products seem like plausible options.

However, after reviewing the APIs and building an initial proof of concept, it became apparent that the system was not merely copying records from one place to another.

The integration required:

  • multiple processing stages;
  • mappings between different data models;
  • transformations across incompatible representations;
  • conditional behavior for particular record types;
  • system-specific validation;
  • custom failure handling;
  • and logic that would continue evolving as the client's requirements became clearer.

The transformation layer was not incidental plumbing around the real system.

The transformation layer was the system.

An off-the-shelf synchronization product would not necessarily have reduced the implementation effort. We still would have needed to understand every API, mapping, exception, and business rule. The difference was that we would have been expressing those requirements through someone else's abstraction rather than ordinary application code.

That could have shifted the work from implementation to configuration and workarounds while giving us less control over the final behavior.

I proposed a purpose-built AWS solution instead. It used serverless functions, queues, managed secret storage, infrastructure as code, and an automated deployment pipeline. The exact services were less important than the architectural decision: the team would own the transformation logic directly.

The proposal was reviewed with the client's enterprise architect, presented alongside other options, approved by the CTO, and ultimately implemented successfully.

The build decision made sense because the custom logic was where the requirements were changing and where the project delivered value.

The Business Boundary Matters More Than the Technical Boundary

It is tempting to classify technologies into categories:

  • build;
  • buy;
  • open source;
  • commercial;
  • managed;
  • self-hosted;
  • infrastructure;
  • or application software.

Those classifications can be useful, but they do not make the decision.

A managed service can provide tremendous flexibility. A self-hosted product can still impose a restrictive model. An open-source tool can reduce vendor dependence while introducing substantial operational burden. A custom application can be either the simplest solution or an unnecessary reinvention.

The relevant boundary is not between custom software and third-party software.

It is between logic that the business must control and complexity that the business would benefit from delegating.

Consider two external services.

The first handles password resets, session expiration, and email verification. Those requirements are relatively stable and common across thousands of applications.

The second attempts to encode a company's changing pricing model, fulfillment rules, customer eligibility requirements, approval workflows, or record-specific transformations.

Both products may be technically capable. Both may have polished APIs, documentation, and administrative tooling. But they occupy very different positions relative to the business.

The first removes undifferentiated work.

The second may become the container into which the organization is forced to fit its own behavior.

Abstractions Are Useful Until They Become Negotiations

Every platform provides an abstraction.

The abstraction determines what kinds of things are easy to express, what kinds are difficult, and what kinds may be impossible.

When the abstraction matches the problem, it creates leverage.

When it does not, every new requirement becomes a negotiation with the platform.

Can this record type have different behavior?

Can this transformation call another service?

Can this workflow branch based on historical state?

Can we replay one stage without replaying the entire process?

Can we preserve the original payload?

Can we implement idempotency using our own key?

Can we introduce an approval step here?

Can we deploy and test this logic through our normal development workflow?

A team should become cautious when ordinary business requirements repeatedly turn into questions about whether the platform allows them.

At that point, the platform is no longer removing complexity. It is becoming a source of complexity.

Understand the Tool's Intended User

The intended user of a product matters because it often reveals what the product is optimizing for.

Some tools are designed for developers who want infrastructure primitives, composability, and direct control. Others are designed to let non-developers assemble standard workflows without writing software.

Neither audience is inherently wrong. The question is whether the product's design assumptions align with the people using it and the system being built.

A tool designed to make common tasks accessible may deliberately restrict the range of behavior users can express. That can be useful when the task is standardized. It becomes dangerous when the business logic is complicated, rapidly changing, or difficult to model in advance.

Ease of initial use should not be confused with long-term suitability.

The relevant questions are:

  • Can the team express the required behavior?
  • Can the system be tested and reviewed properly?
  • Can failures be diagnosed?
  • Can the implementation evolve with the business?
  • Can the organization operate the system without being trapped by its abstraction?

The form of the interface matters only insofar as it helps or hinders the work. Some applications genuinely benefit from rich visual interaction. Data exploration and visualization tools are obvious examples.

But difficulty alone is not a sufficient reason to reject a capable tool. If a tool is appropriate for the job, learning to use it is part of the work.

The goal is not to select the interface that requires the least initial learning. The goal is to select the system that best supports the requirements over its useful lifetime.

Five Questions for a Build-versus-Buy Decision

Before deciding whether to build or buy, I ask five questions.

1. Where is the complexity?

Is the difficult part authentication, distributed storage, event delivery, deployment, transformation logic, pricing logic, approvals, or workflow orchestration?

The location of the complexity matters more than the total number of features.

2. Where is the business value?

Which capabilities differentiate the organization or directly implement its business model?

A company normally should not spend years building commodities. It also should not casually surrender control over the logic that defines how it operates.

3. How likely are the requirements to change?

Stable requirements are easier to delegate to a product.

Frequently changing requirements create pressure against the product's abstraction. The cost of a constraint compounds each time the business asks for something the platform was not designed to express.

4. What constraints does this choice introduce?

Every choice removes some options.

A vendor may impose data models, execution limits, extension mechanisms, deployment processes, pricing tiers, or integration boundaries.

A custom system introduces different constraints, including engineering capacity, maintenance responsibilities, operational risk, and institutional knowledge.

The goal is not to find a choice without constraints. It is to choose constraints that align with the problem.

5. Who owns the operational burden?

Custom software must be monitored, secured, deployed, debugged, and maintained.

External products also create operational responsibilities: vendor management, contract renewal, integration maintenance, usage monitoring, incident escalation, data migration, and contingency planning.

The burden is never eliminated completely. It is assigned to someone.

Build for Actual Requirements, Not Abstract Purity

Choosing to own business-critical logic does not mean designing every system from scratch.

A custom application can still rely heavily on existing primitives:

  • managed databases;
  • cloud object storage;
  • queues;
  • functions;
  • identity providers;
  • container runtimes;
  • deployment platforms;
  • open-source libraries;
  • and hosted observability systems.

The application can remain custom at the layer where custom behavior is necessary while delegating everything below and around it.

This is often the most effective form of software development.

The team owns the decisions that matter while standing on technologies built to solve established infrastructure problems.

It is neither fully custom nor fully purchased. Most good systems are composed from both.

The Simplest Solution May Be the One You Build

There is a tendency to equate purchasing a tool with risk reduction.

Sometimes purchasing a tool reduces risk substantially. It can provide mature security controls, proven scaling behavior, support, compliance capabilities, and years of accumulated operational experience.

But a product also introduces another system that the team must understand.

For a small, well-defined use case, a few functions and a queue may be simpler than adopting an integration platform. The custom solution may involve less configuration, less training, fewer hidden behaviors, and fewer restrictions.

This is especially true when the engineers already understand the relevant infrastructure and the custom implementation is small.

The comparison must use realistic costs.

The build estimate should include maintenance and operations.

The buy estimate should include evaluation, procurement, onboarding, configuration, integration, workarounds, licensing, vendor management, and eventual migration.

Neither option should receive imaginary advantages.

The Real Decision

Build versus buy is not fundamentally a question about whether engineers enjoy writing code.

It is a question about control, leverage, constraints, and organizational focus.

Buy when an existing capability removes difficult but non-differentiating work.

Build when the software must directly express changing, business-specific behavior that would be constrained by another platform's model.

Use existing tools aggressively where they create leverage.

Remain skeptical of tools that require the business to reshape itself around their limitations.

Own the layers where the business changes.

Outsource the layers where the requirements are stable and well understood.

That is the real build-versus-buy decision.

Comments

No comments yet. Be the first!