Module 6: Designing for Change

Painting yourself into a corner

Overview

The previous lesson left you with a warning: if you keep only "don't build anything in advance", you fall into the opposite abyss. This lesson opens that second abyss, under-engineering, and its most graphic symptom: painting yourself into a corner. It's the mistake of refusing to leave any seam —building the cheapest and most direct thing possible, with no boundaries, no abstractions, no margin— so that when the probable change arrives, there's no way in: the only exit is to rewrite. Where over-engineering sinned by excess (preparing for everything imaginable), under-engineering sins by deficit (not preparing for what's almost certain to happen).

And here's the trap that makes this lesson necessary: under-engineering often justifies itself by invoking YAGNI. "We aren't gonna need it, let's make it simple and direct" sounds like the discipline of lesson 5 —but applied to the wrong case—. YAGNI says "don't prepare for the improbable"; under-engineering twists it into "don't prepare for anything", even for the changes the business is clearly going to ask for. The difference is the probability of the change, and confusing them is how a healthy principle (don't over-build) becomes an excuse to paint yourself into a corner. This lesson measures the cost of that mistake: when the change is probable, not leaving the cheap seam costs a fortune in rewriting.

Connection with the module. It's the second abyss (lesson 5: over-engineering; this one: under-engineering; lesson 7: the sensible point). It's the exact mirror of lesson 5: there the mistake was buying options below their break-even point (paying premiums for the improbable); here it's not buying options above their break-even point (refusing the cheap premium for the probable). Together, the two lessons corner the problem from both sides, and leave lesson 7 the task of finding the middle. Frontier with the sister guide: here we don't formally classify the decision or measure technical debt; we work the stance of the architect who recognizes when simplicity became a dead end, and knows that YAGNI has a limit.

An analogy: painting the floor into the corner with no way out

There's a classic scene, almost cartoonish, that gives this mistake its name. Someone paints a room's floor. They start in a corner and cover the floor with fresh paint, advancing. If they think about the future —about how they're going to get out of the room—, they paint retreating toward the door, always leaving themselves a dry path ahead. But if they only think about covering the floor as fast and directly as possible, without looking where they're going, they end up in the corner farthest from the door, surrounded by fresh paint on all sides. They did their job —the floor is painted— but they painted themselves into a corner: there's no way out without stepping on the paint and ruining everything, or without waiting hours for it to dry. The mistake wasn't painting; it was painting without leaving themselves an exit.

Take the image to the module's house. An owner builds cheap and direct: instead of leaving non-structural walls easy to move (the house of lesson 3), they set everything in concrete —every wall, every installation, every pipe, welded, definitive—. It's faster and cheaper to build today. And for today's layout, it works perfectly. The problem arrives with the change that anyone could foresee: a child comes and a room is needed, or work goes remote and an office is needed. In the movable-wall house, that was a weekend. In this one, every wall is reinforced concrete: joining two rooms means demolishing structure, shoring up, permits, a major project. The owner didn't save; they deferred the cost and multiplied it. They paid less on day one and will pay much more on the —foreseeable— day life changes.

Notice the contrast with lesson 5, because it's what defines the opposite abyss. The over-engineering planner paved twelve lanes for traffic that wasn't going to come —over-preparing, for the improbable—. This owner sets everything in concrete ignoring a change that was going to come —under-preparing, for the probable—. Both err, but in opposite directions: one builds the future that doesn't arrive; the other refuses to leave room for the future that does arrive. And under-engineering has a psychological aggravator: it disguises itself as virtue. "I set everything in concrete" sounds like "I did something solid and simple, no complications"; no one confesses it as "I painted myself into a corner". This lesson measures what that corner costs when the probable change knocks on the door.

Worked example: rewriting without a seam vs. the cheap seam

We're going to measure the cost of painting yourself into a corner. The key difference from lesson 5 is in the probabilities: here the changes are not improbable imagined futures; they are probable changes, which Mercado's business is almost certain to ask for. Each has its high probability (p_happens), the cost of leaving the seam today (seam_cost), what it would cost to adapt through it if you left it (adapt_with_seam), and what it would cost to rewrite if you left none (rewrite_no_seam).

We compare two stances:

  • no_seam — under-engineering: leaving no seam. Costs 0 in advance (saves today). But when the probable change arrives —and since it's probable, it almost always arrives—, there's no way in: you have to rewrite, and the expected cost is p × rewrite_no_seam, with rewrite high. It's setting everything in concrete.
  • with_seam — leaving the cheap seam. Pays a small seam_cost in advance, and when the change arrives, only adapts through the seam (adapt, cheap). Its expected cost is seam_cost + p × adapt. It's leaving the movable wall.
# Under-engineering: painting yourself into a corner. The opposite extreme to YAGNI. Here
# the changes are NOT improbable imagined ones: they are PROBABLE changes the business
# is almost certain to ask for. Building the cheapest thing WITHOUT leaving a seam saves today,
# but when the probable change arrives, there's no way in: you have to rewrite.
CHANGES = [
    # (name, p_happens, seam_cost, adapt_with_seam, rewrite_no_seam)
    ("monolith_to_services",      0.90, 8000, 10000, 90000),
    ("second_payments_provider",  0.85, 4000,  5000, 40000),
    ("swap_notification_channel", 0.80, 3000,  4000, 25000),
]

# no_seam (under-engineered): 0 in advance, but catastrophic rework when it arrives.
expected_no_seam = sum(p * rewrite for _, p, _, _, rewrite in CHANGES)
# with_seam: pays a cheap seam; when it arrives, only adapts.
expected_with_seam = sum(seam + p * adapt for _, p, seam, adapt, _ in CHANGES)

print(f"{'probable change':<28}{'p':>6}{'no_seam(p*rewrite)':>20}{'with_seam':>12}")
print("-" * 66)
for name, p, seam, adapt, rewrite in CHANGES:
    ns = p * rewrite
    ws = seam + p * adapt
    print(f"{name:<28}{p:>6.2f}{ns:>20,.0f}{ws:>12,.0f}")
print("-" * 66)
print(f"{'TOTAL expected cost (USD)':<34}{expected_no_seam:>20,.0f}{expected_with_seam:>12,.0f}")
print()
print("When the change is PROBABLE, not leaving a seam isn't 'simplicity': it's")
print(f"painting yourself into a corner. It costs {expected_no_seam / expected_with_seam:.1f}x more to rewrite without a seam")
print("than to have paid the cheap seam. YAGNI isn't 'never prepare anything'.")

What to expect. Running the file, the output is exactly this:

probable change                  p  no_seam(p*rewrite)   with_seam
------------------------------------------------------------------
monolith_to_services          0.90              81,000      17,000
second_payments_provider      0.85              34,000       8,250
swap_notification_channel     0.80              20,000       6,200
------------------------------------------------------------------
TOTAL expected cost (USD)                      135,000      31,450

When the change is PROBABLE, not leaving a seam isn't 'simplicity': it's
painting yourself into a corner. It costs 4.3x more to rewrite without a seam
than to have paid the cheap seam. YAGNI isn't 'never prepare anything'.

Read the table row by row, comparing the two cost columns, because the argument is there.

Notice first the probabilities: 0.90, 0.85, 0.80. All high. These aren't "just in case" imagined futures like lesson 5's; they're changes Mercado is almost certain to ask for. The monolith will almost surely need to be split into services (0.90). There will almost surely be a second payment provider (0.85). The notification channel will almost surely change (0.80). This is the difference that changes everything: when the probability is high, the term p × rewrite is not small —it's almost the full rewrite—.

Look at monolith_to_services. Leaving no seam costs 0.90 × 90000 = 81000 expected: with 90% probability, half the system will have to be rewritten to extract the service from a welded monolith with no internal boundaries. Leaving the seam —a clear interface, separate tables— costs 8000 + 0.90 × 10000 = 17000: the 8000 seam plus a cheap adaptation when it arrives. The difference is brutal: 81000 against 17000. And that pattern repeats in the three rows.

The totals close it: 135000 without a seam against 31450 with a seam —4.3 times more expensive—. When the change is probable, refusing to leave the cheap seam isn't simplicity or saving; it's deferring a cost and multiplying it. Under-engineering saved the seam_cost (8000 + 4000 + 3000 = 15000 total) on day one, and in exchange exposed itself to 135000 expected in rewriting. It traded a small, sure saving for a large, probable cost —the exact mirror of over-engineering's bad deal, in the opposite direction—.

Notice the mechanism, because it's what distinguishes this abyss from the previous one. In lesson 5, over-engineering lost because it paid flexibility for improbable futures (the p × rework terms were small, so YAGNI won). Here, under-engineering loses because it doesn't pay the seam for probable futures (the p × rewrite terms are large, so the seam wins). The same cost structure, inverted by the probability. When p is low, don't prepare (lesson 5); when p is high, prepare (this lesson). Under-engineering's mistake is applying the logic of low probability (YAGNI) to high-probability changes —invoking "we aren't gonna need it" over something you almost surely will need—.

As bars, the disproportion:

Total expected cost (USD): no seam vs with seam, PROBABLE changes
 no_seam    |####################################  135,000
 with_seam  |########                               31,450
             ────────────────────────────────────
 4.3x more expensive to rewrite without a seam than to have
 left the cheap seam for the probable change.

Deep dive: when simplicity becomes a dead end

Under-engineering is treacherous because it rests on real values —simplicity, avoiding premature complexity, YAGNI— and takes them one step too far. It's worth understanding exactly where that step is, because the line between "healthy simplicity" and "dead end" is thin and decides which side of the abyss you fall on.

Healthy simplicity eliminates the accidental; under-engineering eliminates the essential. Simplifying well is removing complexity that adds nothing: the speculative abstraction, the configuration layer no one uses, the premature generalization (everything lesson 5 fights). That's correct and valuable. Under-engineering does something different: it removes a boundary that did add value —the seam between orders and payments, the interface that separated the catalog—, because in the moment it looks like "unnecessary complexity". The difference is whether what you remove protects against a probable change. Removing the movable wall of an area you'll never reconfigure: healthy simplicity. Removing the movable wall of the area where you'll almost surely put the office: painting yourself into a corner. The same action —"we did it direct, without the boundary"— is wise or suicidal depending on the probability of the change the boundary protected.

The disguise of misapplied YAGNI. Under-engineering almost always justifies itself with the language of YAGNI, and that's why it's so hard to fight: it uses the words of the discipline. "We aren't gonna need it, let's do it direct", "let's not complicate with an interface, let orders read the payments tables directly", "a single payment provider, YAGNI the second". The problem isn't YAGNI; it's applying it to the wrong case. YAGNI is a statement about probability: "you aren't gonna need it" is only true if the change is improbable. When someone invokes YAGNI over a change the business is already signaling —finance negotiating the second provider, the monolith creaking under growth—, they're not applying the discipline; they're using its name to justify not preparing what's actually needed. The question that unmasks the disguise is the same as always, inverted: in lesson 5 you asked "is this future probable or only imaginable?" to avoid over-building; here you ask "is this future really improbable, or is it probable and I'm putting the name of YAGNI on my laziness?".

The asymmetry that makes under-engineering especially dangerous. There's a reason why, in doubt, it's wise to lean a little more toward leaving the seam than toward not leaving it, and it's a cost asymmetry. A seam that turned out unnecessary (you left the movable wall and never reconfigured) costs what it cost to build —the small seam_cost, a few thousand—: a bounded waste, annoying but minor. A seam that was missing when needed (you set everything in concrete and the change arrived) costs the rewrite —the high rewrite, tens of thousands— plus the time lost, the risk of breaking things while rewriting, and sometimes the business opportunity lost while you rewrite. The cost of an extra seam is small and bounded; that of a missing seam is large and long-tailed. This asymmetry doesn't mean "leave every seam" (that would be over-engineering); it means that, for changes whose probability is near the break-even point, the error of falling short hurts more than that of overshooting, so the doubt is resolved toward the seam. It's the prudent reverse of lesson 5: there, facing the clearly improbable, don't build; here, facing the probable or the doubtful-but-expensive-to-repair, leave the seam.

Why under-engineering accumulates (and connects with lesson 2). A system built without seams doesn't fail on a single change; it welds itself little by little. Each time the direct route is chosen —orders reads the payments tables, catalog joins with shipping's— a boundary is erased, and the pieces get more stuck together. Over time, the whole system becomes a ball with no internal boundaries where any change is expensive, because nothing is separated from anything. It's the painful monolith of lesson 2, born of a thousand under-engineering decisions, each "simple" in its moment. That's why painting yourself into a corner is rarely a single dramatic event; it's the accumulation of many simplifications that erased boundaries, until one day a trivial change touches five modules and no one understands how it got there. Under-engineering is to boundaries what the drift of lesson 2 is to structure: a deterioration by accumulation that you don't see coming until it has already surrounded you with fresh paint.

Common mistakes

Invoking YAGNI to not leave a seam that's actually needed. What happens: facing a probable change —one the business is already signaling— the architect refuses to leave the seam saying "we aren't gonna need it, let's do it direct", and builds stuck, with no boundary. When the change arrives, it has to be rewritten. Why it happens: YAGNI is a healthy discipline and its language gives cover; it's easy (and comfortable) to apply it to the wrong case and call "simplicity" what is laziness or haste. How to spot it: if "we aren't gonna need it" is invoked over a change the business already mentioned, or if the answer to "what if this thing finance is negotiating arrives?" is "we'll rewrite then", it's misapplied YAGNI. How to fix it: ask honestly "is this change really improbable, or is it probable and I'm putting the name of YAGNI on not wanting to prepare it?" —YAGNI only applies to the improbable—. In the example, refusing the monolith's 8000 seam exposes you to 81000 expected in rewriting.

Erasing boundaries "to simplify" and welding the system. What happens: the direct route is repeatedly chosen —a module reads another's tables, joins are made across domains, everything gets stuck together— because in each individual case "it's simpler this way"; over time the system is left with no internal boundaries and any change becomes expensive. Why it happens: each boundary erasure looks local and healthy ("it's one call fewer, one layer fewer"); the damage only appears in the accumulation. How to spot it: if a change that should be local touches several modules, or if "everything is stuck to everything" and no one knows when it happened, the system was welded by accumulation. How to fix it: distinguish healthy simplicity (removing the accidental: speculative abstractions) from under-engineering (removing the essential: boundaries that protect probable changes); keep the seams that separate domains the business is going to want to move. It's the painful monolith of lesson 2 being born of a thousand simplifications.

Ignoring the asymmetry between an extra seam and a missing seam. What happens: facing a doubtful decision (do I leave the seam or not?), the architect treats the two errors as equally serious and, in doubt, chooses not to leave the seam "for simplicity", without noticing that falling short usually hurts much more than overshooting. Why it happens: the cost of an extra seam is visible today (the seam_cost seen in the budget); that of a missing seam is future and invisible (the rewrite to come). How to spot it: if doubtful decisions are systematically resolved toward "let's not leave it, in case it's over-engineering", without weighing the rework cost, the asymmetry is being ignored. How to fix it: remember that an unnecessary seam costs a bounded few thousand, while a missing seam costs tens of thousands with a long tail (risk, time, opportunity); for doubtful-but-expensive-to-repair changes, resolve the doubt toward the seam. (This doesn't contradict YAGNI: for the clearly improbable, keep not building; the asymmetry only moves the doubt near the break-even point.)

Exercises

Exercise 1 — Healthy YAGNI or corner? For each decision at Mercado, say whether invoking YAGNI ("let's do it direct, without a seam") is the healthy discipline of lesson 5 or the under-engineering of this lesson, and justify with the probability of the change: (a) having orders read and write the payments tables directly, when the business already plans to pull payments out to its own service next semester; (b) not building a plugin layer for third parties, when there's no platform strategy and no one asked for it; (c) hardcoding "a single country, a single currency" throughout the checkout, when marketing already announced the expansion to three countries next year.

See solution
  • (a) Under-engineering (corner). The business already plans to pull payments out to a service next semester: the change is probable and near. Having orders read the payments tables directly erases the seam between the two, and when payments is extracted, all those direct accesses will have to be untangled —a rewrite—. Invoking YAGNI here is applying it to the wrong case: you are going to need it. The healthy seam is for orders to talk to payments through an interface.

  • (b) Healthy YAGNI (lesson 5). With no platform strategy and no demand, a plugin layer for third parties is an imaginable future, not probable. Here "let's not build it" is the correct discipline: don't prepare for the improbable. It's exactly the over-engineering lesson 5 avoids. Not leaving that flexibility paints you into no corner, because the change almost surely doesn't come.

  • (c) Under-engineering (corner). Marketing already announced the expansion to three countries: multi-country and multi-currency are probable changes with a date. Hardcoding "one country, one currency" throughout the checkout is setting everything in concrete right where life is going to change; when the expansion arrives, the whole checkout will have to be touched. Invoking YAGNI over something already announced is putting the name of the discipline on not wanting to prepare. The healthy seam is to isolate the currency and the country instead of scattering them hardcoded.

The pattern: (a) and (c) are probable/announced changes → leaving a seam is healthy preparation, not leaving it is painting yourself into a corner; (b) is imaginable → not building is healthy YAGNI. The same phrase ("let's do it direct, YAGNI") is wise or suicidal depending on the probability of the change.

Exercise 2 — The mirror of lesson 5. The text says under-engineering and over-engineering are "the same cost structure, inverted by probability". Explain what that means by comparing the mechanism of why each one loses, using the terms p × rework (lesson 5) and p × rewrite (this lesson).

See solution

The two lessons use the same structure: they compare paying in advance (the flexibility / the seam) against paying the expected cost of adapting later (p × cost_to_repair). What changes between them —and what inverts the result— is the probability p.

In lesson 5 (over-engineering), the changes were improbable (p small, summing 0.25 expected). With p small, the term p × rework is small: the expected cost of not preparing and adapting later is low, because you almost never have to adapt. That's why pre-building the flexibility (paying in advance, guaranteed) loses: you pay a lot for sure to avoid a little improbable. Not preparing (YAGNI) wins.

In this lesson (under-engineering), the changes are probable (p high: 0.90, 0.85, 0.80). With p high, the term p × rewrite is large —almost the full rewrite—: the expected cost of not preparing and rewriting later is enormous, because you almost always have to rewrite. That's why not leaving the seam (saving in advance) loses: you save a little for sure to expose yourself to a lot probable. Preparing (leaving the seam) wins.

That is: it's literally the same formula (pay_today vs p × repair_later), and the winner flips depending on whether p is below (lesson 5: don't prepare) or above (this: prepare) lesson 3's break-even point. The two abysses are the same calculation mis-resolved in opposite directions: over-engineering prepares when p is low; under-engineering doesn't prepare when p is high. And the break-even point —the probability threshold— is the line that separates the two, exactly what lesson 7 uses to find the middle.

Exercise 3 — The asymmetry in doubt. A Mercado architect faces a doubtful decision: a change whose probability they estimate around the break-even point —it could go either way—, and whose rewrite, if the seam were missing, would be expensive. They say: "in doubt, better not leave it, so as not to fall into over-engineering". Using the idea of the cost asymmetry, explain why their tie-breaking rule is miscalibrated for this case, and how they should resolve the doubt.

See solution

Their tie-breaking rule ("in doubt, don't leave it") ignores the asymmetry between the cost of an extra seam and that of a missing seam, and that's why it's miscalibrated exactly for this case —a doubtful change whose rewrite would be expensive—.

The two possible errors don't cost the same. If they leave the seam and the change doesn't arrive (extra seam), they waste the seam_cost —a few thousand, a small and bounded cost: you know exactly how much was lost and it ends there—. If they don't leave the seam and the change arrives (missing seam), they pay the rewrite —tens of thousands— plus a long tail of costs: the risk of breaking things while rewriting under pressure, the time lost, and sometimes the business opportunity lost while rewriting instead of advancing. The cost of overshooting is small and closed; that of falling short is large and open.

When two errors don't cost the same, the tie-breaking rule shouldn't treat them as equal. For this case —probability near break-even and expensive rewrite—, the doubt should be resolved toward leaving the seam, not against, because the error of falling short hurts much more than that of overshooting. Their rule "in doubt don't leave it" would only be well-calibrated if the two errors cost roughly the same, or if the rewrite were cheap —but they themselves said it would be expensive—.

The nuance that avoids the misunderstanding: this does not contradict YAGNI nor push you into over-engineering. The asymmetry only moves the decision in the doubtful zone (near the break-even point). For clearly improbable changes, the rule remains not to build (lesson 5) —there's no doubt to resolve there—. The asymmetry says: when you really don't know and erring toward under-engineering would be expensive, lean toward the seam. The well-calibrated architect's rule isn't "in doubt don't prepare" nor "in doubt prepare"; it's "in doubt, prepare if falling short would hurt much more than overshooting" —which here is the case—.

Summary and next step

In this lesson you opened the module's second abyss: under-engineering, painting yourself into a corner. You saw, with the person who paints the floor into the corner with no way out and with the concrete house, that refusing to leave any seam isn't simplicity or saving when the change is foreseeable —it's deferring a cost and multiplying it—. And you measured it: for probable changes (0.90, 0.85, 0.80), leaving no seam costs 135000 expected in rewriting against 31450 to leave the cheap seam —4.3 times more—. You understood that it's the exact mirror of lesson 5 (the same formula, inverted by probability), that it disguises itself as misapplied YAGNI ("we aren't gonna need it" over something you are going to need), that it accumulates by welding boundaries until it recreates the painful monolith, and that the cost asymmetry —a missing seam hurts more than an extra one— tilts doubtful-and-expensive decisions toward the seam.

Before moving on you should be able to: distinguish healthy YAGNI (not preparing for the improbable) from under-engineering (not preparing for the probable, under the name of YAGNI); explain why the two abysses are the same calculation inverted by p; and apply the cost asymmetry to resolve a doubt near the break-even point.

You now have the two abysses: over-engineering (over-preparing, for the improbable) and under-engineering (under-preparing, for the probable). Lesson 7 joins them and finds the path between them: designing the seams at the sensible point. You'll see that designing for change is not designing for every imaginable change, but building the seam only where the change is probable enough to pay for itself, and deferring the rest —and execute the right-sizing that beats both extremes at once—. The module's synthesis, with numbers.

Resources

  • Martin Fowler, "Design Stamina Hypothesis" (2007) — martinfowler.com/bliki/DesignStaminaHypothesis.html. The text that frames the two abysses: there's a design point below which (under-engineering) the system becomes rigid and expensive to change, and above which (over-engineering) it over-invests. This lesson is the "below" side. In English.
  • Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004) — the manual of what to do after painting yourself into a corner: how to introduce seams into a welded, boundaryless system to be able to change and test it. The cure for accumulated under-engineering. In English.
  • Sam Newman, Building Microservices, 2nd ed. (O'Reilly, 2021), chapter on monoliths and extraction — on why a monolith with no internal boundaries is very expensive to split later, and how to leave the seams (bounded contexts, modules) that make the future extraction cheap. The monolith_to_services case from the example. In English.
  • Andrew Hunt and David Thomas, The Pragmatic Programmer, 20th Anniversary Edition (Addison-Wesley, 2019), topics "Decoupling" and "Reversibility" — on why coupling everything (erasing boundaries) closes doors expensively, and why good design keeps the pieces separable. In English.