Simplicity
The cheapest line of code is the one you didn't write.
Tony Hoare wrote it down in 1973: simplicity is a necessary condition. The half-century since has not loosened the rule. Every additional concept the next reader carries is a tax. F4 is the discipline of refusing the tax unless the requirement pays for it.
Opinion
I've watched F4 get agreed in the retro and violated by lunchtime. The deadline closes; a parameter gets added “for the next time it is needed”; an abstraction gets extracted on the second occurrence rather than the third; a configuration option ships in case someone asks. Each move is rational on its own; the compounded artefact is a codebase no-one wants to open. The fix is mechanical: hard caps the linter enforces, and a review culture that asks what requirement does this serve? of every parameter, flag, and abstraction.
The hardest sub-discipline is rejecting the “easy” trap. Generating a four-parameter helper from one example is easy: the agent does it in seconds and the diff looks tidy. The artefact is more complex than the duplication it replaced, and the reader pays for it on every callsite. Hickey's 2011 distinction is the cure: ask whether the code has fewer interleaved concerns, not whether it was quicker to write. Optimise for the artefact, not for the keystroke.
Copy a note and link
Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.
Write the simplest code that solves the problem in front of you. Don't add a parameter, a flag, or an option for a caller that doesn't exist yet. If a feature isn't needed today, don't build it today — wait until someone actually needs it. Less code is fewer bugs, faster reviews, and easier changes later. /tenet/simplicity/F4
AI eyes only
Rule: build for the requirement at hand. Speculative parameters cost more than they save.
Reject: function parameters with no current caller using a non-default value. Reject: boolean flags that toggle behaviour. Reject: abstraction layers with one concrete implementation. Reject: configurability nobody asked for.
Generate: the smallest function that satisfies the test. Hardcode until duplication forces a parameter. Inline the wrapper that has one caller.
Diagnostic: every parameter must have a current caller passing a non-default value. Every branch must be reached by a real codepath. Otherwise delete.
Why?
- Fewer lines to read, review, and maintain. Code is liability; less of it is less liability.
- Bugs are roughly proportional to the size and branching of the codebase. Halve the branches and you halve the bug-surface.
- Reviewers can hold a small change in working memory. Complexity is what reviewers rubber-stamp because no-one can audit ten branches at once.
- Simple code is deletable code. webpro's “optimise for deletion” turns out to be the strongest practical metric of code health.
- Counter-intuitive: simple code is easier to extend than over-built code, because the extension point is the cheap addition, not the unwiring of the wrong abstraction.
- LLMs perform better against simpler artefacts — fewer interleaved concerns means fewer hallucinated APIs and fewer parallel helpers.
- When the speculative caller does arrive, the cost of adding the parameter then is almost always lower than the compounded cost of carrying it from now until then.
Origins
C. A. R. Hoare stated the rule formally in 1973: simplicity is the necessary condition for any other quality the language designer might want.1Hints on Programming Language Design (Stanford AIM-224 / CS-403, October 1973). The keynote at the SIGACT/SIGPLAN Symposium on Principles of Programming Languages, Boston, 1973. Source of the phrase 'utmost simplicity is a necessary condition' and the verbatim 'self-documenting code' (the latter relevant to F5). The argument applied to programming-language design but transferred cleanly to programming itself: without simplicity, no other quality can be evaluated.
Edsger Dijkstra provided the philosophical anchor. The often-quoted line “Simplicity is a prerequisite for reliability” is what Rich Hickey opens his 2011 talk Simple Made Easy with, and it remains the load-bearing claim. Niklaus Wirth 1995 wrote the engineering manifesto: reducing complexity and size must be the goal in every step; a programmer's competence should be judged by ability to find simple solutions.2A Plea for Lean Software, IEEE Computer 28(2), 64–68, February 1995. Lesson 7 of the nine numbered lessons.
Frederick Brooks 1986 named the distinction the entire principle rests on: essence vs. accidents. Some difficulty is inherent to the problem; the rest is craft failure. F4 governs the latter. There is no silver bullet for essential complexity; for accidental complexity, F4 is one of the few levers that consistently works.3No Silver Bullet — Essence and Accidents of Software Engineering, IEEE Computer 20(4), 1987. Originally UNC TR86-020, 1986. The essence/accidents distinction is the philosophical frame F4 sits inside.
Thomas McCabe 1976 quantified the threshold: cyclomatic complexity v(G) ≤ 10. Modern lint rules (ESLint complexity, Sonar S3776) descend directly. Eric S. Raymond 2003 codified the Unix tradition as Rule 5: Simplicity; webpro/programming-principles, the most-starred community catalogue, places KISS at #1 of the Generic category.4A Complexity Measure, IEEE Transactions on Software Engineering SE-2(4), 308–320, December 1976. Section IV recommends v(G) ≤ 10 as the upper bound. Every modern complexity-cap lint rule descends from this paper.
Rich Hickey 2011 reframed the whole conversation. The roots: sim+plex is one fold; complex is braided together. Easy derives from “to lie near”: easy is relative to the writer's familiarity. Simple is objective. Most of what passes for “simple code” is actually familiar code. The discipline of F4 is to optimise for the property of the artefact, not for the familiarity of the keystroke.5Simple Made Easy, Strange Loop 2011. The talk that distinguished simple (objective, no interleaving) from easy (relative, familiar). Coined the verb 'complecting' as the pejorative for braiding-together.
Quotes
A necessary condition for the achievement of any of these objectives is the utmost simplicity in the design of the language. Without simplicity, even the language designer himself cannot evaluate the consequences of his design decisions.
Reducing complexity and size must be the goal in every step — in system specification, design, and in detailed programming. A programmer's competence should be judged by the ability to find simple solutions, certainly not by productivity measured in “number of lines ejected per day”.
The roots of this word are sim and plex, and that means one fold or one braid or twist. The opposite of this word is complex, which means braided together or folded together. Being able to think about our software in terms of whether or not it's folded together is sort of the central point of this talk.
I believe the hard part of building software to be the specification, design, and testing of this conceptual construct, not the labor of representing it and testing the fidelity of the representation. If this is true, building software will always be hard. There is inherently no silver bullet.
Evidence
Twenty external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.
- 01Earliest formal statement: simplicity is a *necessary condition*. The whole tradition of language and library design rests on this paragraph.
- 02A Plea for Lean SoftwareSupportsEngineering manifesto for parsimony. Lesson 7 — reducing complexity must be the goal in every step — is the operational form of F4.
- 03Names the distinction F4 rests on: essential complexity cannot be removed; accidental complexity can. F4 governs the latter.
- 04Simple Made Easy (Strange Loop)SupportsThe reframing: simple = no interleaving (objective property of the artefact); easy = familiar (relative to the writer). Optimise for simple.
- 05A Complexity MeasureSupportsQuantified threshold: v(G) ≤ 10. Modern lint rules (ESLint complexity, Sonar S3776) all descend from this paper.
Sixteen sources across four decades. The supporting line runs from Hoare 1973 through Wirth, Brooks, Hickey, and McCabe: the canonical case for simplicity as a measurable design property. Modern voices extend the row beneath the canon; the qualifying voice does not deny F4, but argues that “small modules” is the wrong proxy and that depth-of-interface matters more than count-of-units. Both can be true at once, and the F1 + F4 stack respects the qualification: F4 governs accidental complexity at all scales; F1 governs where to draw the unit boundary.
Examples
// Before: speculative options bag for callers that don't exist yet.interface FormatOptions { precision?: number; locale?: string; currency?: string; showSymbol?: boolean;}function formatPrice(amount: number, opts: FormatOptions = {}): string { const precision = opts.precision ?? 2; const currency = opts.currency ?? "GBP"; const locale = opts.locale ?? "en-GB"; return new Intl.NumberFormat(locale, { style: "currency", currency, maximumFractionDigits: precision }).format(amount);}
// After: one job, no flags. Add complexity when a caller asks.function formatPrice(pence: number): string { return `£${(pence / 100).toFixed(2)}`;}
Enforcement
Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.
| Rule | Tool | Catches |
|---|---|---|
| complexity | ESLint core | branchy code that compounds reader effort. Cap mirrors McCabe 1976. |
| max-lines-per-function | ESLint core | functions past the human-working-memory threshold. |
| sonarjs/cognitive-complexity | eslint-plugin-sonarjs | code that is technically simple but reads as a maze — the metric tracks reader effort, not raw branches. |
| max-params | ESLint core | five-parameter functions that have stopped describing what they do — extract a parameter object. |
| max-depth | ESLint core | pyramid-of-doom nesting. Use guard clauses to flatten. |
| no-unused-vars | ESLint core | dead code that survived a refactor. Kill it; don't dress it up. |
eslint.config.mjsconfiguration snippet
import tseslint from 'typescript-eslint';
import sonarjs from 'eslint-plugin-sonarjs';
export default tseslint.config({
files: ['**/*.{ts,tsx}'],
plugins: { sonarjs },
rules: {
'max-lines': ['error', { max: 300, skipBlankLines: true, skipComments: true }],
'max-lines-per-function': ['error', { max: 15, skipBlankLines: true, skipComments: true }],
'max-params': ['error', 3],
'max-depth': ['error', 3],
'complexity': ['error', { max: 8 }],
'sonarjs/cognitive-complexity': ['error', 10],
'no-unused-vars': 'error',
'no-useless-return': 'error',
'no-useless-concat': 'error',
'sonarjs/no-redundant-jump': 'error',
'sonarjs/no-collapsible-if': 'error',
}
});AI rules
.cursor/rules/f4-simplicity.mdc---
description: Prickles F4 — Simplicity (KISS / YAGNI)
globs: "**/*.{ts,tsx,js,jsx,py,java,php}"
alwaysApply: false
---
## Prickles F4 — Simplicity (KISS / YAGNI)
The cheapest line of code is the one you didn't write. Build for the requirement at hand, not the speculative future. Speculative generality is debt with no creditor.
Simplicity is a property of the artefact (Hickey: "no interleaving"). Easy is a property of the writer's familiarity. Optimise for simple, not for easy. Keep the writer uncomfortable so the reader is comfortable.
When the code grows past a hard cap — function length, parameter count, cyclomatic complexity, nesting depth — extract per F1. The extraction is the point. The cap is just the linter's reminder that you've stopped reading and started reciting.
YAGNI is a temporal subset of simplicity: do not add the parameter, the flag, the abstraction, the option, until the second caller actually arrives. Premature flexibility is more expensive than the duplication it pretended to prevent.
Essence vs accidents (Brooks): some complexity is inherent to the problem and cannot be removed. F4 governs accidental complexity — the kind we add. The discipline is recognising which is which.Repo layout, CI, and ESLint wiring for these paths live on /implementation — not repeated on every tenet.
Counter-argument
The steelman has two heads. The first is Brooks' essence: some problems are inherently complex; aggressive simplification produces a shanty architecture that cannot represent the domain. In distributed systems, financial regulation, real-time scheduling and type theory the simple-looking version is the wrong version. The second is Ousterhout's deep modules argument: small modules with thin interfaces look simple but force the caller to compose a thicket of tiny units, increasing total complexity. KISS naively applied produces shallow modules; shallow modules are more complex than the deep module they decomposed.
Counter-argument retort
The first head is right and changes nothing. F4 governs accidental complexity, the complexity that gets authored. Brooks himself draws the line. Where the domain is genuinely complex, the artefact will be too; F4's job is to ensure the artefact is no more complex than the domain demands. The phrase to defend in review is “is this complexity essential or accidental?”, not “is this simple?”.
The second head is absorbed by F4 + F1, not refuted by them. Hickey's definition (simple means no interleaving) is compatible with deep modules. A deep module with one responsibility and a small interface is simpler than the seven shallow modules it could be decomposed into; the seven each carry unrelated concerns interleaved at the call site. Ousterhout's critique is a critique of bad SRP, not of F4. F1 says one job per unit; F4 says no accidental complexity at any scale; the two together produce deep modules where the domain rewards them, and small modules where it does not.
Default stance for application code: hard caps on cyclomatic complexity, function length, parameter count; YAGNI on every parameter and abstraction; the third callsite earns the extraction. Everything else is an exception that gets defended in the PR description.
Notes
- [1]C. A. R. Hoare — Hints on Programming Language Design (Stanford AIM-224 / CS-403, October 1973). The keynote at the SIGACT/SIGPLAN Symposium on Principles of Programming Languages, Boston, 1973. Source of the phrase 'utmost simplicity is a necessary condition' and the verbatim 'self-documenting code' (the latter relevant to F5).
- [2]Niklaus Wirth — A Plea for Lean Software, IEEE Computer 28(2), 64–68, February 1995. Lesson 7 of the nine numbered lessons.
- [3]Frederick Brooks — No Silver Bullet — Essence and Accidents of Software Engineering, IEEE Computer 20(4), 1987. Originally UNC TR86-020, 1986. The essence/accidents distinction is the philosophical frame F4 sits inside.