---
title: "The Production Cost of \"Just-in-Case\" Code"
description: "Applying YAGNI in practice: the price of adding flexibility you don't need today, repaid daily in maintenance, readability, and bug surface"
url: https://sade.dev/en/journal/the-cost-of-just-in-case-code/
lang: en
author: "Muhammet Şafak"
published: 2026-06-06
updated: 2026-09-06
section: Journal
tags: ["architecture","simplicity","decisions","opinion"]
summary: "Flexibility added just in case is not free code; it is a debt paying interest every day until the day it is used — in reading cost, maintenance, bug surface, and most expensively lock-in to an abstraction drawn from a guess. The guess is almost always wrong, because the moment you know the domain least is the project's first week. Add flexibility when the need is proven, and save it for one-way doors."
---

# The Production Cost of "Just-in-Case" Code

> Flexibility added just in case is not free code; it is a debt paying interest every day until the day it is used — in reading cost, maintenance, bug surface, and most expensively lock-in to an abstraction drawn from a guess. The guess is almost always wrong, because the moment you know the domain least is the project's first week. Add flexibility when the need is proven, and save it for one-way doors.

In code reviews I keep running into this comment: `// another provider might show up later`. Right below it sits an interface with a single implementation, a factory that builds it, and a config key nobody ever reads. There is no second provider on the roadmap — only the possibility of one.

People assume this code was free to write. It wasn't. Every bit of flexibility added "in case we need it later" is a debt that pays interest every day until the day it's actually used.

## What is speculative generality?

Speculative generality is building flexibility today for a requirement that doesn't exist today. A strategy pattern where one line would do, a generic parameter where one type would do, an event system for a single call.

The justification always arrives in the same words: "later", "just in case", "let's keep it extensible". Each one is a prediction about the future. And prediction is the most expensive thing in a codebase.

## The line items on the bill

Unused flexibility doesn't sit idle; it pays these line items every day:

- **Reading cost.** Everyone who reads the code looks for "is there a second use of this?", fails to find one, and moves on confused. Flexibility makes you keep thinking about a scenario that doesn't exist.
- **Maintenance cost.** The abstraction has to be updated alongside the real code beneath it on every touch. Even an empty layer demands maintenance.
- **Bug surface.** Code that isn't running can still break code that is — a wrong branch, a bug forgotten in a dead branch, a path that was never tested.
- **Lock-in to the wrong abstraction.** This is the most expensive one. The interface you draw from a guess today almost never fits tomorrow when the real need arrives — but by then ripping it out is harder than writing it from scratch.
- **Test burden.** Every extra layer of indirection lengthens the test setup and increases the number of mocks.

The "complexity budget" from the [boring architecture](/en/journal/why-boring-architecture) piece applies here too: speculative generality spends that budget while delivering nothing.

## The prediction is almost always wrong

The bitterest part of "just-in-case" code is this: when you actually do need something later, the thing you need isn't the thing you predicted.

The second payment provider eventually arrives — but it arrives with an async webhook flow your interface never assumed. The second tenant arrives — but with a separate schema, not row-level isolation. Flexibility drawn ahead of time turns out to be an obstacle rather than a help when it meets the real requirement: you have to remove it first.

The moment you know the domain least is the project's first week; that's exactly when you make your worst predictions about the future. In the [modular monolith piece](/en/journal/why-i-start-with-a-modular-monolith) I argued that boundaries drawn in the first week are almost certainly wrong — the same holds for every early abstraction.

## Applying YAGNI in practice

"You Aren't Gonna Need It" isn't a license for laziness; it's a timing rule: add flexibility when the need is **proven** — not when it's predicted.

In practice:

- **Write for today's known case.** If there's one provider, write the one provider. Flat, direct, readable.
- **The rule of three.** Wait for three real uses of a pattern before abstracting it. Two might be coincidence; three is a pattern.
- **Open the seam later.** When the second provider actually arrives, extracting the interface is an IDE refactor — a few hours. There's no return on paying those few hours today.

The asymmetry is here: abstracting flat code later is easy. Flattening a wrong abstraction later is hard. That asymmetry favors YAGNI.

## When is upfront flexibility justified?

It isn't about deferring everything to the last moment either. The distinction is whether the decision is **reversible**:

- **Reversible decisions (two-way door).** The internal structure of a class, a function's signature, code organization. Changing these later is cheap — no need to keep them flexible today.
- **Hard-to-reverse decisions (one-way door).** The database schema, an API contract you've exposed, a data format, a security boundary. Here, thinking ahead isn't speculation but responsibility — because the cost of getting it wrong is a data migration in production.

The rule: be generously simple **inside the code**; be cautious **where you expose to the outside and write to data**. Save flexibility for where undoing it is genuinely expensive.

---

"We'll need it later" expresses a fear, not a requirement. Grow code with evidence, not fear.

The easiest code to delete is the code that was never written.

## Frequently asked

**What is speculative generality?**

Building flexibility today for a requirement that does not exist today: a strategy pattern where one line would do, a generic parameter where one type would do, an event system for a single call. The justification always arrives in the same words — later, just in case, let us keep it extensible — and every one of them is a prediction about the future.

**What does unused flexibility cost?**

Five line items, paid every day. Reading cost, because everyone looks for a second use and does not find one. Maintenance cost, because even an empty layer is updated alongside the real code beneath it. Bug surface, because code that is not running can still break code that is. Lock-in to the wrong abstraction, which is the most expensive. And test burden, since every layer of indirection lengthens the setup and adds mocks.

**When is upfront flexibility justified?**

When the decision is hard to reverse. The internal structure of a class, a function signature, code organization are two-way doors and changing them later is cheap. A database schema, an exposed API contract, a data format and a security boundary are one-way doors, and thinking ahead there is responsibility rather than speculation, because the cost of getting it wrong is a data migration in production.


## Sources

- [Yagni](https://martinfowler.com/bliki/Yagni.html) — Martin Fowler
- [2016 Letter to Shareholders: one-way and two-way doors](https://www.aboutamazon.com/news/company-news/2016-letter-to-shareholders) — Amazon
