The cost of change
Code is read and changed far more than it is written, so the dominant cost of software is the cost of changing it. "Good code" is not an aesthetic — it is code whose next change is cheap. This lesson defines the cost-of-change lens the whole track uses.
A function you wrote six months ago needs a one-line behaviour change. You open the file and the one line turns into a day: the logic is tangled with three other concerns, there are no tests so you can’t change it safely, the names lie about what the code does, and a “small” edit breaks two callers you didn’t know existed. The code worked — it shipped, it ran in production. So why is changing it so expensive?
Because working code and changeable code are different properties, and the second one is what costs money over a system’s life. This whole track is about the second property. Before any rule about naming or SOLID makes sense, you need the lens that justifies all of them: the dominant cost of software is the cost of changing it.
After this lesson you can explain why the read-to-write ratio makes readability an economic argument, not a style preference; describe how coupling causes a single logical change to “amplify” into many edits; and use “what will the next change cost?” as the test you apply to every piece of code in this track.
Code is read and modified far more than it is written. You write a function once. After that it is read dozens of times — during reviews, debugging, onboarding, and every future change. Empirically the ratio of time spent reading versus writing is heavily lopsided toward reading. That single fact flips the optimisation target: code “optimised” to be fast to type (terse names, clever one-liners, copy-paste) is optimised for the rarest activity. Code optimised to be fast to read and change is optimised for what actually dominates the budget.
So “readability” is not taste. It is the cheapest lever on the largest cost.
The lifetime cost of a system is dominated by maintenance, not initial construction. The first version is the small part. Most of the money goes into the changes that come after: new requirements, bug fixes, adapting to a new dependency, scaling. A useful mental model is the cost-of-change curve — for a given codebase, how expensive is the next feature as the system grows?
- In a codebase that resists change (high coupling, no tests, poor names), the curve bends sharply upward: each feature costs more than the last, because every change drags more of the system with it.
- In a codebase designed for change (cohesive modules, clear seams, tests), the curve stays close to flat: feature N costs about what feature N−1 did.
The disciplines in this track are the things that keep that curve flat.
Coupling turns one logical change into many physical edits — “change amplification”. Suppose the business rule “tax is 20%” changes to “tax depends on region”. If that rule lives in one place behind a clear name, the change is one edit. If the literal 0.2 is duplicated across twelve call sites, or the tax logic is inlined into rendering, ordering, and reporting code, the same logical change now requires finding and editing all of them — and missing one is a bug.
The number of places you must touch to make one conceptual change is a direct measure of how change-hostile the code is. Good structure minimises it; that is most of what cohesion and the SOLID principles are for.
“Software entropy”: without active effort, changeability decays. Each quick patch that ignores structure — a flag bolted on, a special case wedged in, a copy-paste “just this once” — adds a little disorder. Disorder compounds: the messier the code, the harder the next change is to do cleanly, so the next change is also a patch, which adds more mess. Left alone, a codebase trends toward the high-cost end of the curve. This is why “we’ll clean it up later” rarely happens by itself and why the boy-scout rule (leave code a little better than you found it) is an economic strategy, not a moral one.
The test you will apply all track: “what will the next change cost?” Every rule that follows — intention-revealing names, small cohesive functions, single responsibility, depending on abstractions, refactoring under tests — is justified by the same question. A name is good if it makes the next reader’s change cheaper. A module boundary is good if it confines the blast radius of a likely change. A test is good if it lets you change code without fear. When two designs compete, the senior tiebreaker is rarely “which is prettier” — it is “which makes the change we expect next cheaper, without overpaying now for changes that may never come.”
Watch one logical change amplify. Here is a tax rule duplicated across the codebase:
// pricing.ts
const total = subtotal + subtotal * 0.2;
// invoice.ts
const tax = lineTotal * 0.2;
// report.ts
const projectedTax = forecast * 0.2;The rule “tax is 20%” is expressed three times, as the bare literal 0.2. When the rule changes to “tax depends on region”, every site must change — and each must now learn about regions, which they had no reason to know about before. The change amplifies: one business decision, three (or thirty) edits, plus the risk of missing one.
Now the same rule behind a single boundary:
// tax.ts — the one place the rule lives
export function taxFor(amount: number, region: Region): number {
return amount * rateFor(region);
}
// callers depend on the name, not the number
const total = subtotal + taxFor(subtotal, region);
const tax = taxFor(lineTotal, region);
const projectedTax = taxFor(forecast, region);The region change is now one edit inside taxFor. Callers are untouched because they depended on what (tax for an amount) instead of how (multiply by 0.2). Nothing here is clever — it is just the rule named and placed once. That is the entire game: arrange code so the changes you expect are cheap and local. Notice the cost was paid up front by naming the concept; the payoff is every future change to it.
▸Why this works
Why not just extract everything to be safe? Because changeability is a bet, not an absolute. Every abstraction you add is itself code to read and a guess about which change will come. Extracting a concept that never varies adds indirection without buying flexibility — you pay the reading cost and get nothing back. The skill is not “always abstract”; it is predicting which changes are likely and making those cheap, while leaving unlikely ones simple. The track’s later units (heuristics, “the wrong abstraction”) are about this judgment. For now: the lens is cost-of-expected-change, not flexibility for its own sake.
▸Common mistake
A common misread: “good code = code that works.” Working code clears the lowest bar — it produces the right output today. It says nothing about what the next change will cost. Plenty of code that passes every test is a nightmare to modify, and plenty of beautiful-looking code is over-abstracted and equally hard to change. Don’t evaluate code by whether it runs; evaluate it by the cost of the next realistic change. That is the only definition of “good” this track will use.
Two implementations both pass all current tests. One inlines a duplicated business rule across many files; the other names the rule and places it once. By this track's definition, which is 'better code' and why?
The dominant cost of software is the cost of changing it, because code is read and modified far more than it is written and a system’s lifetime is mostly maintenance. Coupling causes change amplification — one logical change becomes many physical edits — and software entropy means changeability decays unless you actively defend it. So “good code” is not an aesthetic: it is code whose next realistic change is cheap and local. Every rule in this track — naming, cohesion, SOLID, refactoring under tests — is justified by one question you should now apply to all code you read: what will the next change cost? The remaining lessons in this unit sharpen that lens before we start naming the specific forces that raise or lower the cost.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.