Skip to content

Greenspun's Tenth Rule

The aphorism that any sufficiently complicated C or Fortran program contains an ad hoc, bug-ridden implementation of half of Common Lisp — because features a language withholds do not vanish but migrate inside the program as defective re-implementations.

Core Idea

Greenspun's Tenth Rule (the "tenth" is the joke — there are no preceding nine) states: any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. When a base language withholds features a growing program needs — first-class functions, runtime code construction, macros, symbolic manipulation — those features do not disappear; they migrate inside the program, assembled piecemeal from what the language provides, and arrive defective. The choice is not whether the feature exists but where it lives and how cleanly it is built.

How would you explain it like I'm…

The Taped-Together Toolbox

Imagine building a huge castle with a toy set that has no wheels. If your castle needs a moving drawbridge, you'll end up making wobbly wheels out of tape and string. Greenspun's Tenth Rule says big computer programs do the same: when the language is missing tools, programmers build shaky homemade copies of them inside the program.

Missing Tools Get Rebuilt Badly

Programmers write programs in languages, and some languages have fewer built-in tools than others. Greenspun's Tenth Rule is a joke saying that any big enough program in a simple language like C ends up secretly containing a clumsy, buggy copy of half of a much richer language called Common Lisp. That happens because when the program needs a missing tool, programmers patch one together from the parts they do have. Each patch seems small, but together they make a slow, messy mini-language nobody planned. The real lesson is that the needed features show up anyway; the only choice is whether you build them carefully or by accident.

Accidental Lisp Interpreter

Greenspun's Tenth Rule says any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. The 'tenth' is a joke, since no earlier rules exist. The serious point is that when a language lacks features a growing program needs, such as functions you can pass around, code that builds code, or garbage collection, those features don't vanish. They get rebuilt inside the program as workarounds: a homemade rules engine, a template system with its own if-statements, a custom expression evaluator. Put together, these add up to a poor interpreter nobody planned or recognizes. So the real choice is not whether you get the feature, but whether you build it deliberately and well or by accident and badly.

 

Greenspun's Tenth Rule, attributed to Philip Greenspun and humorously numbered with no preceding rules, states that any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. It names a failure pattern in language choice and design: when the base language lacks features the domain demands—first-class functions, runtime code construction, macro-like transformation, symbolic data manipulation, dynamic dispatch, garbage collection, REPL-style interactivity—those features migrate into the application. They are assembled from whatever primitives are available, as hand-rolled rules engines, string-template languages, reflection-based plugin frameworks, or custom evaluators. Each is a locally minimal workaround; collectively they are an unrecognised interpreter, untested and undocumented. The structural claim is that the feature will exist regardless; what varies is where it lives and how well it is built. Deliberate internalization (an embedded DSL, a real macro system, a designed plugin interface) beats accidental accretion, which costs more to maintain and can't be reused. The expressivity gap between language and domain sets a complexity floor that discipline alone cannot remove.

Scope of Application

The rule lives within programming-language design and the software-engineering culture around it, across contexts where programs are written in a language too narrow for their domain.

  • Programming-language theory — expressiveness-versus-simplicity debates and rationales for macros and first-class functions.
  • Software-architecture critique — the reviewer's label for ad-hoc rule engines, config DSLs, homegrown templating.
  • Programmer folklore — preserved in collections of programming wisdom as an argument for expressive base languages.
  • Its dual neighborhood — paired with the Lisp tarpit and the Turing tarpit as the ends of the expressivity trade-off.

Clarity

Naming the rule makes a scattered set of code smells legible as one diagnosis. A hand-rolled rules engine, a templater with its own conditional syntax, and a reflection-heavy plugin loader stop being unrelated lapses of judgment and become one recurring failure: withheld features migrating inside the program as defective re-implementations. That moves the design argument off the personal axis (this subsystem is badly built) onto the structural one (the expressivity gap forced it into existence), where it can be resolved — and sharpens the question to where should this capability live.

Manages Complexity

A sprawl of seemingly unrelated subsystems collapses to a single generator: each is the smallest local workaround for one capability the base language withheld, and their union is a defective re-implementation of the missing features. The maintainer tracks one quantity — the expressivity gap between language and domain — from which the shape follows: the wider the gap, the larger the internalized re-implementations, and maintenance cost scales with the size of the accreted embedded language, not the count of feature requests.

Abstract Reasoning

The rule licenses a diagnostic (read scattered odd subsystems back to one generator, the expressivity gap; run it in reverse to predict an embedded language's presence), a quantitative-predictive move (cost scales with the accreted language's size), an interventionist move keyed to its central branch (the capability will be internalized; the only variable is deliberately or accidentally), and a boundary-drawing move (it bites only past a complexity threshold and only when the domain demands the withheld expressivity).

Knowledge Transfer

Within programming-language design the rule transfers as mechanism — the diagnostic, the quantitative prediction, the deliberate-versus-accidental branch, and the boundary conditions carry intact across PL-theory debates, architecture critique, and its dual concepts, because each is the same substrate. Beyond software the general pattern travels as the parent it specializes — the inner-platform effect (a system denied an abstraction grows a worse re-implementation of it) — recurring in a spreadsheet-turned-database or an org reinventing a worse HR system. Loose invocations there are metaphor; the Lisp-and-C cargo stays home. Were an Inner-Platform Effect prime added, Greenspun's tenth would be its named PL specialization.

Relationships to Other Abstractions

Local relationship map for Greenspun's Tenth RuleParents appear above the current abstraction, mutual partners to the right, and children below. Node labels state whether each abstraction is prime or domain-specific; colors identify relation types.Greenspun'sTenth RuleDOMAINDomain-specific abstraction: Inner-Platform Effect — is a kind ofInner-PlatformEffectDOMAIN

Current abstraction Greenspun's Tenth Rule Domain-specific

Parents (1) — outgoing typed relations

  • Greenspun's Tenth Rule is a kind of Inner-Platform Effect Domain-specific

    Greenspun's Tenth Rule is the programming-language species of the inner-platform effect, where a program lacking sufficiently expressive host abstractions grows an ad hoc, incomplete language implementation inside itself.

Hierarchy paths (3) — routes to 2 parentless roots

Neighborhood in Abstraction Space

Greenspun's Tenth Rule sits in a moderately populated region (57th percentile for distinctiveness): it has near-neighbors but no dense thicket of look-alikes.

Family — Software Evolution Laws & Code Smells (17 abstractions)

Nearest neighbors

Computed from structural-signature embeddings · 2026-10-25