Every KISS decision falls into one of these case classes. The goal of the worked examples below is that no cell is left uncovered — you should never meet a situation on the job you did not rehearse here.
Cell
Case class
The question it forces
Covered by
A
Clever one-liner vs. plain code
Is fewer characters really simpler?
Ex 1
B
Deep nesting (positive "reduce n")
Can flattening lower the pair count?
Ex 2
C
Premature abstraction (one user)
Should I abstract now?
Ex 3
D
Zero / empty input (degenerate)
Does simple still handle nothing?
Ex 4
E
"And no simpler" (over-simplification is a bug)
When is deleting a check wrong?
Ex 5
F
Justified complexity (the case where KISS says "keep it")
When is more code the simple choice?
Ex 6
G
Limiting behaviour — n growing large
How fast does cost blow up?
Ex 7
H
Real-world word problem
Turn a vague ask into the simplest build
Ex 8
I
Exam-style twist
Spot the trap that looks like KISS
Ex 9
Notice the "signs / quadrants / zero / limits" pattern from math has a direct twin in coding:
sign of the effect → does a change raise or lowern? (Ex 1, 2)
the true zero → n=0: no parts at all, C=(20)=0 (nothing to relate — the absolute floor)
zero input → empty list, None, no user (Ex 4)
degenerate → one implementation only, n=1 (Ex 3)
limiting value → n→ large (Ex 7)
the razor edge → "as simple as possible but no simpler" (Ex 5, 6)
List the parts of Version X. The reader must simultaneously track: a>b, a<=b, the two multiplications, and the addition that glues them. That is n=5 interacting sub-expressions.
Why this step? Cognitive load counts sub-expressions you juggle at once, not source lines.
Compute C for X.C=(25)=25⋅4=10.
Why this step? Ten pairs is the number of "does this interact with that?" checks the reader silently runs.
List the parts of Version Y. One comparison a>b, one early return, one fallthrough return: n=3.
Why this step? Guard-style code lets you dismiss a branch the moment it fires, so parts don't linger in memory.
Compute C for Y.C=(23)=23⋅2=3.
Compare. Version X has fewer lines (1 vs 4) but more than 3× the interaction load (10 vs 3). Line count and reasoning-cost point opposite ways.
Recall Verify (sanity check)
Plug back: (25)=10, (23)=3, ratio 10/3≈3.3. The one-liner is quantitatively harder despite being shorter — exactly the parent note's "fewest lines ≠ simplest" mistake, now with a number. ✓
Nested version — count live pairs. At the deepest point all 3if blocks are open and the ship action sits inside them, so all n=4 parts are held at once. Every pair among them is live: (24)=24⋅3=6 live pairs — the 3 condition–condition pairs plus the 3 condition–action pairs.
Why this step? Nesting forces every condition and the action to co-exist in memory; the earlier count of 3 wrongly ignored the condition–action pairs.
Guard-clause version — count live pairs. Each guard checks one condition and returns. When you read guard 2, guard 1 already exited; the action is reached only after all guards pass, on its own line. So at any instant at most one part is live, and one part forms 0 pairs.
Why this step? Early return dismisses a part (makes it non-live), so no pair using it is live.
Net effect. Live pairs fall from (24)=6 down to 0, with the total parts n=4 unchanged — you kept every feature but stopped holding them together.
Why this step? This is the exact mechanism KISS praises: same behaviour, far fewer simultaneous interactions.
Recall Verify
Nested live pairs =(24)=24⋅3=6 (3 condition–condition + 3 condition–action); guard clauses give 0 because at most one part is live at a time and (21)=0. The refactor removes 6 simultaneous pairs with zero loss of behaviour. ✓
Count parts of the abstract design. Base class, subclass, factory, plus the call site that wires them: n=4 concepts to understand before you learn it just prints a string.
Why this step? Every named indirection is a "part" — the reader must chase each one.
Compute its load.C=(24)=24⋅3=6.
Count parts of the plain function.def greet(name): return f"Hello, {name}" — one part: the function itself. n=1.
Why this step? One implementation needs no dispatch machinery.
Compute its load.C=(21)=21⋅0=0. (A single thing has no pairs — nothing to relate it to.)
Why this step? This is the degenerate low end: n=1⇒C=0, the cheapest a built thing can be.
Verdict. Abstraction added 6 pairs to buy flexibility that has zero current users. Add the pattern when the second greeting arrives — that is the principle "You Aren't Gonna Need It" (YAGNI): don't build for imagined futures, which is one concrete way of achieving KISS.
Recall Verify
(24)=6, (21)=0. Cost of the pattern with one user =6−0=6 pure overhead. ✓
def total(prices): s = 0 for p in prices: s += p return s
With prices = [] the loop body never runs, s stays 0, returns 0.
Why this step? The degenerate input is the one most refactors forget — test it first. (This is the input zero; contrast the structural zero n=0 from the intro.)
Trace on [10, 20, 30].0+10+20+30=60.
Why this step? Confirm the non-empty path still works after simplifying.
Conclusion. Here the extra empty-guard was genuinely unnecessary — the boring loop already returns 0 for []. Deleting it is correct KISS (unlike Ex 5, where deletion is a bug).
Why this step? This shows the distinction: delete a guard only after proving the simple path covers the edge.
Recall Verify
sum([])==0 and 10+20+30==60. The simple loop is total-correct on both the zero case and the normal case. ✓
With the clamp (s += max(0, p)): 10+max(0,−5)+3=10+0+3=13.
Why this step? This encodes the real requirement "no negative line."
Without the clamp (naive s += p): 10+(−5)+3=8.
Why this step? Show the wrong answer the over-simplification produces.
Judge. The results differ (13 vs 8). The clamp was needed behaviour, not decoration. Removing it is over-simplification — a bug — this is the "but no simpler" razor edge.
Why this step? Contrast with Ex 4: there the guard was redundant; here it is essential. KISS never asks you to drop a required check.
Recall Verify
Correct clamped total =10+0+3=13; broken naive total =10−5+3=8; 13=8, so the clamp changes behaviour and must stay. ✓
Compute load of Option A. Five checks: total pairs (25)=25⋅4=10. But written as independent guard clauses (Ex 2 logic), each check returns and is dismissed before the next, so at any reading instant at most one check is live — live pairs =(21)=0. ("At any moment" = while your eyes are on one specific check line; the definition box above gives the precise meaning of live.)
Why this step? Total n can be moderate while live load is minimal, because guards dismiss parts one at a time.
Compute load of Option B. A cryptic regex bundles all 5 rules into one opaque token the reader must decode as a unit: effective n = the 5 rules plus the decoding step =6, and none can be dismissed (you must parse the whole regex to understand any rule). So all (26)=26⋅5=15 pairs are live at once.
Why this step? Density (Ex 1's lesson) keeps every part live simultaneously, making B heavier despite looking shorter.
Verdict. The complexity is inherent to the problem — 5 real rules exist. KISS says pick the form where the required complexity is most readable: Option A (live pairs 0) over Option B (live pairs 15). So "keep it simple" sometimes means keep more lines.
Why this step? Covers the case class students miss: KISS ≠ "always less code."
Recall Verify
Option A total pairs (25)=10 but live pairs 0 (one guard live at a time, (21)=0); Option B (26)=15, all live. Option A wins on live load; the 5 checks are irreducible because 5 real requirements exist. ✓
Why this step? The endpoints n=1⇒C=0 (degenerate: nothing to relate) and large n show the shape.
2. Doubling ratio. From n=10 (C=45) to n=20 (C=190): ratio =45190≈4.2.
Why this step? Doubling parts roughly quadruples load — the quadratic signature.
3. Limit intuition. As n grows without bound, C=2n2−n is dominated by the 2n2 term, so we write C∼21n2. Here the symbol "∼" means asymptotically equivalent: the ratio C/(21n2) approaches 1 as n→∞. In plain words, for large n the cost behaves just like21n2 — genuinely quadratic.
Why this step? Establishes the limiting law behind the whole principle: every deleted part is worth more than the previous one.
Recall Verify
(22)=1,(25)=10,(210)=45,(220)=190; ratio 190/45=4.222…≈4. Quadratic confirmed. ✓
Extract the real requirement. "Must email" = 1 channel exists now. "Maybe later" = 0 channels required now.
Why this step? KISS solves the problem you have (parent playbook move #1).
Cost the speculative build. Factory + 4 channel classes + wiring =n=6; C=(26)=15 — for 3 channels nobody asked to build yet.
Why this step? Quantify the speculative-generality tax.
Cost the KISS build. One function send_welcome_email(user): n=1, C=(21)=0.
Why this step? Minimum for a built thing — build exactly what's needed.
Answer. Ship the single email function (C=0). Introduce an abstraction only when the second channel is actually requested — then the pattern earns its 15-pair cost by removing real, proven duplication (DRY) instead of imagined future duplication. Until that second channel exists, the speculative factory is 15 pairs of pure overhead, so the disciplined move is to wait.
Why this step? Closes the loop: complexity is justified only by duplication that has actually arrived, never by a "maybe."
Recall Verify
Speculative (26)=15 vs KISS (21)=0; overhead avoided =15 pairs for features with 0 current users. ✓
Count the if-chain. 3 conditions handled and exited (guard-style): n=3; because each exits, live pairs ≈0.
Why this step? Baseline to beat.
Count the dict version. 3 keys + 1 default + 1 .get mechanism = n=5; total pairs C=(25)=25⋅4=10, and none dismissible because you must understand the whole dispatch to read any branch — so all 10 are live.
Why this step? The "trap": "fewer lines" hides that the dict adds a dispatch concept (n up from 3 to 5).
Judgement. For a fixed set of 3 cases, the dict is not a KISS win — it raises n and pays for extensibility no one requested (Ex 3 / Ex 8 pattern). If cases were data-driven and many (say n≫10), the dict would win because a growing if chain scales linearly in lines but a dict lookup stays flat. The correct exam answer depends on the number of cases — the twist is that neither form is universally simpler.
Why this step? Rewards the reader who checks the actual n instead of the slogan "fewer lines = simpler."
Recall Verify
if-chain n=3⇒ live pairs 0; dict (25)=10 (all live). For 3 fixed cases the if-chain wins; the dict only pays off as case-count grows large. ✓
Which example covers zero/empty input? ::: Ex 4 (Cell D) — the boring loop returns 0 for []; and the intro covers the structural n=0 floor.
Which example shows deleting a check is a bug? ::: Ex 5 (Cell E) — dropping the max(0,p) clamp gives 8 instead of 13.
Which example shows KISS keeping more code? ::: Ex 6 (Cell F) — 5 explicit ZIP checks beat one dense regex.
Which example proves the quadratic limit? ::: Ex 7 (Cell G) — doubling n from 10→20 multiplies load by ≈4.2.
Which example is the exam trap where the answer is "it depends on n"? ::: Ex 9 (Cell I) — dict vs if-chain flips with case count.
See also: YAGNI, DRY, Single Responsibility Principle, Cyclomatic Complexity, Premature Optimization, Code Readability.