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 — attributed to Philip Greenspun and quoted without a preceding nine rules (the numbering is the joke) — states: any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. The claim names a precise failure pattern in programming-language design: when a base language withholds expressive features that a growing program actually needs — first-class functions, runtime code construction, macro-like transformation, symbolic data manipulation, dynamic dispatch, garbage collection, REPL-like interactivity — those features do not disappear from the program; they migrate inside it, assembled piecemeal from the pieces the language does provide, and they arrive defective. The hand-rolled rules engine, the string-template system with its own conditional syntax, the reflection-heavy plugin framework, the custom expression evaluator: each emerged as the smallest available workaround for one missing capability, and their aggregate is a Lisp interpreter — slow, untested, undocumented, and unrecognised as such.

The structural commitment inside the joke is that the choice is not whether the feature exists but where it lives and how cleanly it is implemented. A program written in a language too narrow for its domain must internalize the missing expressivity; the only question is whether that internalization is done deliberately (an embedded DSL, a proper macro system, a carefully designed plugin interface) or accidentally (the accreted workarounds). The accreted version reproduces the needed feature at worse quality, higher maintenance cost, and with no reuse across projects. The expressivity gap between language and domain sets a floor for complexity that cannot be avoided by discipline alone — it can only be met with better tooling, a more expressive language, or an intentional internal language that acknowledges what it is.

Structural Signature

Sig role-phrases:

  • the under-expressive base language — a host language withholding a class of operations the program will come to need (first-class functions, runtime code construction, macros, symbolic manipulation, dynamic dispatch, garbage collection)
  • the demanding domain — a problem space that genuinely requires those withheld operations
  • the complexity threshold — the "sufficiently complicated" point past which the gap begins to bite (below it, no embedded language forms)
  • the expressivity gap — the single load-bearing quantity: the distance between what the language provides and what the domain demands
  • the incremental local workarounds — the series of smallest-available fixes, each re-implementing one missing piece (hand-rolled rules engine, runtime formula evaluator, reflection-heavy plugin loader, templater grown its own conditionals)
  • the accreted embedded language — the aggregate of those workarounds: a slow, untested, undocumented, unrecognized re-implementation of the missing features
  • the deliberate-versus-accidental branch — the only controllable variable: the capability will be internalized, the question is whether as an acknowledged DSL/macro system/designed interface or as piecewise accretion
  • the gap-scaled maintenance burden — cost scales with the size of the accreted embedded language, not the count of feature requests
  • the remedy — meet the unavoidable complexity floor with a more expressive language, better tooling, or an internal language that admits what it is

What It Is Not

  • Not a literal claim about Lisp code. The program does not contain Common Lisp source; "half of Common Lisp" is the vivid vehicle for a structural point — that withheld expressive features (first-class functions, runtime code construction, macros, symbolic manipulation) migrate inside the program as defective re-implementations. Reading it as "there's literally a Lisp interpreter in there" mistakes the joke's imagery for its claim.
  • Not the tenth of ten rules. There is no canonical list of nine preceding rules; the "tenth" numbering is itself part of the joke. Hunting for the other nine, or treating the rule as one entry in some enumerated catalogue, misreads a one-line aphorism as a numbered series.
  • Not a put-down of the programmers. The rule moves the diagnosis off the personal axis ("this subsystem is badly built," "they should have known better") and onto the structural one: the expressivity gap between language and domain forced the subsystem into existence. The accreted re-implementation is the predictable output of an under-expressive base language meeting a demanding domain, not a lapse in someone's judgment.
  • Not an injunction never to embed a language. The rule does not say "don't reinvent the feature" — once the program outgrows its language the missing capability will be internalized; that is not optional. The controllable variable is only how: deliberately, as an acknowledged DSL, real macro system, or designed plugin interface, versus accidentally, as piecewise accretion. The advice is to choose where the capability lives, not to refuse it.
  • Not a universal law. It bites only past a complexity threshold ("sufficiently complicated") and only when the domain genuinely demands the withheld expressivity. A small program, or one whose domain never needs runtime code construction, will not grow the embedded language; reading the rule onto it is a false positive. The complexity floor is set by the gap, so a wide gap is required for the prediction to fire.

Scope of Application

The rule lives within programming-language design and the software-engineering culture around it, across the contexts where programs are written in a base language too narrow for their domain; its reach is within that domain. The genuine cross-domain analogues (a spreadsheet accreting into a bad database, an org reinventing a worse HR system) belong to the parent inner-platform-effect pattern, and invoking "Greenspun's rule" there is metaphor.

  • Programming-language theory and design — cited in expressiveness-versus-simplicity debates (Felleisen's expressiveness hierarchy) and in design rationales for first-class functions, macro systems, and gradual typing.
  • Software-architecture critique — the reviewer's label for ad-hoc rule engines, configuration DSLs, plugin frameworks, and homegrown templating that have grown inside applications on an under-expressive base language.
  • Programmer folklore and aphoristic literature — preserved in collections of programming wisdom (Norvig commentary, Coders at Work, Stack Overflow lore) as a canonical argument for choosing expressive base languages.
  • Its dual-concept neighborhood — paired with the Lisp tarpit (a language so expressive every project re-implements its own subset) and the Turing tarpit (power technically present but unusable) as the two ends of the expressivity/convenience trade-off, with Greenspun naming the under-expressive end.

Clarity

Naming the rule makes a scattered set of code smells legible as one diagnosis. Before the label, a reviewer confronts the hand-rolled rules engine, the string-template system with its own conditional syntax, the runtime expression evaluator, the reflection-heavy plugin loader, and the cron-string interpreter as so many unrelated lapses in judgment — each looks like a place where some team "should have known better." The rule re-describes them as a single recurring failure: features the base language withheld migrating inside the program as defective re-implementations. That reframing moves the design argument off the personal axis (this subsystem is badly built) and onto the structural one (the expressivity gap between language and domain forced this subsystem into existence), which is the form in which it can actually be resolved.

The sharper question it lets a designer ask is where should this capability live. The rule's core distinction is between a missing feature that is internalized deliberately — an acknowledged embedded DSL, a real macro system, a designed plugin interface — and one that accretes accidentally as the smallest local workaround at each step; the two produce the same capability at very different quality, maintenance cost, and reuse. Holding "the feature is needed" separate from "the feature is implemented well" is what turns a vague unease about a sprawling codebase into a decidable choice: meet the unavoidable complexity floor with a more expressive language, better tooling, or an internal language that admits what it is — rather than hoping discipline alone will keep the missing Lisp from reassembling itself piecemeal.

Manages Complexity

A large under-expressive codebase presents the maintainer with a sprawl of seemingly unrelated subsystems — a database-backed rules engine that parses strings at runtime, a code-generation step emitting classes per policy type, a reflection-heavy plugin loader, a custom formula evaluator, a cron-string interpreter, a document templater that has grown its own conditional syntax. Treated individually, each is its own intricate artifact with its own design history, its own bugs, its own maintenance story, and the codebase reads as N independent problems. Greenspun's rule collapses that sprawl to a single generator: each subsystem is the smallest local workaround for one capability the base language withheld, and the union of them is a defective re-implementation of the missing expressive features. The maintainer stops tracking N unrelated subsystems and instead tracks one quantity — the expressivity gap between the base language and what the program's domain actually demands (first-class functions, runtime code construction, macros, symbolic manipulation, dynamic dispatch, garbage collection). From that single parameter the qualitative shape follows: the wider the gap, the larger and more numerous the internalized re-implementations, and the higher the maintenance cost, which scales with the size of the accreted embedded language rather than with the count of feature requests. The rule also imposes a clean branch on the design choice. The missing capability will be internalized — that is not optional once the program outgrows its language — so the only live question is how: deliberately, as an acknowledged embedded DSL, real macro system, or designed plugin interface that is built once, tested, documented, and reused; or accidentally, as the piecewise accretion that arrives slow, untested, and unrecognized. The analyst reads off which branch a codebase is on, and the remedy that follows — a more expressive language, better tooling, or an intentional internal language that admits what it is — directly from the gap and the manner of internalization, instead of re-diagnosing each subsystem as a separate lapse in someone's judgment.

Abstract Reasoning

Greenspun's rule licenses a small family of reasoning moves inside programming-language design and code review, all turning on the single quantity it isolates — the expressivity gap between base language and domain.

The diagnostic move runs from a surface signature to a hidden cause. A reviewer who finds a hand-rolled rules engine that parses strings at runtime, a templater grown its own conditional syntax, a reflection-heavy plugin loader, a runtime formula evaluator — scattered, individually-odd subsystems — infers a common generator: the base language withheld a capability the domain demanded (first-class functions, runtime code construction, macros, symbolic manipulation, dynamic dispatch), and the program internalized it piecemeal. The inference also runs in reverse and predictively: knowing a language lacks code-as-data facilities but the domain needs them, the analyst expects an interpreter, DSL, or plugin layer to be present somewhere in any sufficiently grown codebase, and goes looking for it. The presence of the embedded language is read off the gap, not discovered by accident.

The quantitative-predictive move estimates magnitude from the gap. The wider the expressivity gap, the larger and more numerous the internalized re-implementations, and — the rule's sharpest forward prediction — maintenance cost scales with the size of the accreted embedded language, not with the count of feature requests that produced it. A team can therefore forecast that a metaprogramming-heavy domain on an under-expressive base will grow an expensive runtime/DSL layer regardless of discipline, because the complexity floor is set by the gap and cannot be lowered by care alone.

The interventionist move follows from the rule's central branch: the missing capability will be internalized once the program outgrows its language — that is not optional — so the only controllable variable is how. The reviewer reads which branch a codebase is on (deliberate embedded DSL, real macro system, designed plugin interface, built-once-and-tested; versus accidental piecewise accretion) and prescribes accordingly: adopt a more expressive base language, supply better tooling, or convert the accidental internal language into an acknowledged, documented, reusable one. Each prescription is a prediction — that quality, maintenance cost, and reuse improve — and the move's force is to relocate the capability to where it can be implemented cleanly rather than to deny that it is needed.

The boundary-drawing move fixes when the rule bites and where it does not. It applies only past a complexity threshold ("sufficiently complicated") and only when the domain genuinely demands the withheld expressivity; a small program, or one whose domain never needs runtime code construction, will not grow the embedded Lisp, and reading the rule onto it is a false positive. The rule also marks its dual boundary — the Lisp tarpit, a language so expressive every project re-implements its own opinionated subset, versus the Turing tarpit, where the power is technically present but unusable — both special cases of the expressivity-versus-convenience trade-off, with Greenspun naming the under-expressive end specifically. Holding "the feature is needed" separate from "the feature is implemented well" is the discipline these moves share: it converts a vague unease about a sprawling codebase into a decidable choice about where a capability should live.

Knowledge Transfer

Within programming-language design and software engineering the rule transfers as mechanism. The diagnostic (read scattered odd subsystems — a string-parsing rules engine, a templater grown its own conditional syntax, a reflection-heavy plugin loader, a runtime formula evaluator — back to one generator, the expressivity gap that forced the missing capability to migrate inside the program), the quantitative prediction that maintenance cost scales with the size of the accreted embedded language rather than the count of feature requests, the central branch (the capability will be internalized; the only controllable variable is whether deliberately or accidentally), and the boundary conditions (it bites only past a complexity threshold and only when the domain genuinely demands the withheld expressivity) carry intact across the subfields that share the substrate. They apply unchanged to PL-theory debates over expressiveness versus simplicity (Felleisen's expressiveness hierarchy; design rationales for first-class functions, macros, gradual typing), to software-architecture critique (ad-hoc rule engines, configuration DSLs, plugin frameworks, homegrown templating), and to its dual concepts (the Lisp tarpit and Turing tarpit as the two ends of the expressivity/convenience trade-off). The transfer is mechanical here because each of these is the same substrate — programs written in a base language too narrow for their domain — so the embedded-Lisp signature and the remedy (a more expressive language, better tooling, or an acknowledged internal DSL) refer to the same machinery throughout.

Beyond software the transfer is case (B), a shared abstract mechanism that recurs while the rule's Lisp-and-C cargo stays home-bound. The general pattern that travels is the parent the aphorism specializes: the inner-platform effect — a system, denied an abstraction by its host, grows a worse re-implementation of that abstraction inside itself — and more broadly, systems grow toward the expressivity their environment requires, whether or not the host supplies it cleanly. That pattern genuinely recurs across substrates: a spreadsheet accreting into a bad database, a database schema re-implementing a database inside itself, an organization reinventing a worse human-resources or workflow system, a configuration format that sprouts its own conditionals and variables. In each, the structural lesson holds — the needed capability will appear somewhere; choose where it lives and build it deliberately — but it travels as the parent (inner-platform effect / required-expressivity growth), not as "Greenspun's tenth rule." What does not travel is everything that makes the rule itself: the specific withheld features it names (macros, homoiconicity, first-class environments, runtime code construction), the Lisp-vs-C framing, and the software-specific remedies (adopt a more expressive base language, prefer composable libraries over framework-internal DSLs) are all programming-language furniture with no counterpart in a spreadsheet or an org chart. This is why the loose cross-domain invocations ("every spreadsheet contains a bad database," "organizations always reinvent a worse HR system") are metaphor: they borrow the rule's vivid shape but rename its components and drop the expressivity-gap mechanism, reading as analogy rather than the rule traveling. Strip the programming vocabulary and the residue is a witty restatement of the inner-platform effect — which is exactly the portable structure, and exactly why Greenspun's rule is a domain-specific abstraction (indeed a piece of programmer folklore worth preserving as a cultural reference) rather than a prime. Were an inner_platform_effect or system_grows_toward_required_expressivity prime added to the catalogue, Greenspun's tenth would become its named programming-language specialization. The honest cross-domain lesson is to carry that parent; "Greenspun's tenth rule," as named, carries Lisp-and-C baggage that does not and should not travel. (See Structural Core vs. Domain Accent.)

Examples

Canonical

The defining instance is the archetype the aphorism describes (it is programmer folklore, not the finding of a study). Picture a large business application written in a language without first-class functions or runtime code construction, tasked with an ever-growing set of pricing and eligibility rules. Rather than recompile for each rule change, the team stores rules as strings in a database and writes an interpreter to parse and evaluate them at runtime. To express "if X and Y then Z" the interpreter grows conditionals; to reference other values it grows variables; to reuse logic it grows something like functions. Piece by piece — a formula evaluator here, a template engine that sprouts its own control flow there, a reflection-based plugin loader — the program accretes an unacknowledged, slow, untested interpreter for a small programming language. No one designed a language; the aggregate of local workarounds is one.

Mapped back: The host language without first-class functions or runtime code is the under-expressive base language; the growing rules workload is the demanding domain. Each string-parser, variable, and conditional is one of the incremental local workarounds, and their union is the accreted embedded language — assembled because the expressivity gap forced the withheld capability to migrate inside, unrecognized and defective.

Applied / In Practice

Embedding a real scripting language is the deliberate branch put into practice, and it is now standard engineering. Redis (the in-memory data store) embeds Lua so users can run atomic server-side scripts; the nginx web server exposes Lua via OpenResty for request handling; countless game engines embed Lua for gameplay logic; and GNU Emacs was built from the outset on a genuine Lisp (Emacs Lisp) for its extension layer. In each case the designers recognized that their application would need user-programmable behavior — the withheld capability would be internalized — and chose to internalize it as a complete, documented, tested language rather than let an ad hoc mini-interpreter accrete. The embedded interpreter is reused across every project on the platform, maintained once, and understood as what it is, which is exactly the quality-and-cost improvement the rule predicts for choosing the deliberate branch.

Mapped back: The base application (Redis, nginx, Emacs) is the under-expressive base language facing the demanding domain of user-programmable behavior. Embedding Lua or Emacs Lisp resolves the deliberate-versus-accidental branch toward the deliberate side: the internalized capability is the accreted embedded language built once and acknowledged, so the gap-scaled maintenance burden is paid deliberately — the rule's prescribed remedy of an internal language that admits what it is.

Structural Tensions

T1: Deliberate versus accidental internalization (the only controllable variable). The rule's structural commitment is that once a program outgrows its language, the missing capability will be internalized — that is not optional; the only live question is whether it is done deliberately (an acknowledged embedded DSL, a real macro system, a designed plugin interface) or accidentally (piecewise accretion of the smallest local workarounds). The tension is that discipline cannot prevent the embedded language, only shape its quality: the feature's existence is forced by the expressivity gap, so a team's control is confined to where the capability lives and how cleanly it is built, never to whether it appears. The advice is not "don't reinvent the feature" but "choose the branch," and refusing the choice does not avoid the internalization — it just defaults to the accidental branch. Diagnostic: Is the missing capability accreting as piecewise workarounds (accidental), or built once as an acknowledged, documented, reusable internal language (deliberate)?

T2: Structural diagnosis versus personal blame (moving off the individual axis). The rule re-describes a scattered set of code smells — the hand-rolled rules engine, the templater grown its own conditionals, the reflection-heavy plugin loader — not as lapses of judgment ("they should have known better") but as the predictable output of an under-expressive base language meeting a demanding domain. The tension is that the aphorism's vivid, mocking phrasing ("ad hoc, informally-specified, bug-ridden, slow") reads as a put-down of the programmers, even though its actual claim exonerates them: any competent team on the same gap hits the same floor. The joke's tone fights its structural point, and reading the tone rather than the claim relocates the diagnosis back onto the personal axis the rule exists to move it off. Diagnostic: Are you attributing the accreted interpreter to the team's judgment (misreading the tone), or to the language-domain expressivity gap that forced it into existence (the rule's actual claim)?

T3: Complexity floor versus discipline (what care can and cannot lower). The expressivity gap sets a floor for complexity that cannot be avoided by discipline alone — it can only be met with a more expressive language, better tooling, or an intentional internal language. The tension is that discipline (code review, standards, refactoring) is exactly what teams reach for, and it genuinely improves quality within the accidental branch, but it never removes the internalization or lowers the floor; the missing Lisp reassembles itself piecemeal regardless of care. So effort spent on discipline can feel like addressing the problem while leaving its structural cause untouched, and the only floor-lowering moves are the ones that change the gap itself. Diagnostic: Can the proposed remedy lower the complexity floor (adopt a more expressive base, supply real tooling, build an intentional DSL), or does it only tidy the accretion (discipline, which leaves the floor intact)?

T4: Embedded-language size versus feature-request count (the counterintuitive cost driver). The rule's sharpest forward prediction is that maintenance cost scales with the size of the accreted embedded language, not with the count of feature requests that produced it. The tension is that budgets, estimates, and staffing are naturally keyed to feature count — the visible stream of requests — while the burden actually tracks the size of the unrecognized interpreter those requests assembled. So cost keeps growing even as feature requests slow, because each request quietly enlarged a language no one acknowledges maintaining, and a team measuring the wrong quantity is blindsided by a maintenance load that does not correlate with the work it thought it was doing. Diagnostic: Is maintenance cost here tracking the count of features delivered, or the size of the accreted embedded language they aggregated into?

T5: Bites past a threshold versus false-positive over-application (when the rule actually fires). The rule applies only past a complexity threshold ("sufficiently complicated") and only when the domain genuinely demands the withheld expressivity — first-class functions, runtime code construction, macros. A small program, or one whose domain never needs those, will not grow the embedded Lisp, and reading the rule onto it is a false positive. The tension is that the aphorism is so vivid and quotable that it invites seeing the embedded interpreter everywhere, turning a conditional diagnosis into a reflexive one; the same memorability that makes it useful makes it over-applied, and a wide expressivity gap is required for the prediction to fire at all. Diagnostic: Is the program past the complexity threshold and does its domain genuinely demand the withheld expressivity, or are you pattern-matching the rule onto a case where the gap never opens?

T6: Autonomy versus reduction (programming folklore or the parent inner-platform effect). "Greenspun's Tenth Rule" is a named piece of programmer folklore with cargo that stays home: the Lisp-versus-C framing, the specific withheld features it names (macros, homoiconicity, first-class environments, runtime code construction), and the software-specific remedies (adopt a more expressive base language, prefer composable libraries over framework-internal DSLs). The portable structure beneath it is the parent — the inner-platform effect, in which a system denied an abstraction by its host grows a worse re-implementation of it inside itself, and more broadly, systems grow toward the expressivity their environment requires. That pattern recurs as a spreadsheet accreting into a bad database or an org reinventing a worse HR system, but it travels as the parent, not as the rule; those invocations are metaphor. Diagnostic: Resolve toward the inner-platform-effect parent when carrying the lesson to spreadsheets, org charts, or configuration formats; toward Greenspun's rule when diagnosing an under-expressive codebase's accreted, unacknowledged interpreter.

Structural–Framed Character

Greenspun's Tenth Rule sits on the framed side of the spectrum — best read as framed-leaning: a mildly-evaluative failure-pattern aphorism wholly constituted by the practice of programming, whose portable core (the inner-platform effect) is a genuine structural pattern but one that belongs to the parent, not to the rule. On evaluative_weight it reads mildly framed: the aphorism names a defect — "ad hoc, informally-specified, bug-ridden, slow" — and so carries a critical charge, though the entry is careful that its actual claim is structural and exonerating ("not a put-down of the programmers"; any competent team on the same gap hits the same floor), so it sits below a flat "anti-pattern" verdict. On human_practice_bound it reads strongly framed: the rule is constituted by software engineering and dissolves without it — strip away languages, programs, and withheld features and there is nothing left; its very subject is an artifact of programming practice. Institutional_origin is likewise framed: it is a named piece of programmer folklore (Greenspun), a cultural artifact of a professional community, its numbering ("tenth" with no preceding nine) itself an in-joke. On vocab_travels it reads framed: Lisp, C, macros, homoiconicity, first-class environments, runtime code construction, embedded DSL are pinned to programming and have no literal referent off it. And on import_vs_recognize the transfer is the entry's explicit "case-B" pattern — within programming-language design the mechanism is recognized intact across PL theory, architecture critique, and the tarpit dual-concepts, but beyond software the recurrences (a spreadsheet accreting into a bad database, an org reinventing a worse HR system) are import-by-analogy that rename the components and drop the expressivity-gap mechanism.

The portable structural skeleton is the inner-platform effect — a system denied an abstraction by its host grows a worse re-implementation of that abstraction inside itself (more broadly: systems grow toward the expressivity their environment requires). That pattern genuinely recurs across substrates, but it is exactly what Greenspun's rule specializes from its parent, not what makes "Greenspun's tenth rule" itself travel: the cross-domain reach belongs to the inner-platform effect (were an inner_platform_effect prime catalogued, Greenspun's would become its programming-language specialization), while the Lisp-versus-C framing, the specific withheld features, and the software-specific remedies stay home. Its character: a mildly-evaluative, folklore-originated, wholly programming-bound failure-pattern aphorism whose structural force is the inner-platform-effect pattern it dresses in Lisp-and-C vocabulary — framed-leaning, structural only in the parent it specializes.

Structural Core vs. Domain Accent

This section decides why Greenspun's Tenth Rule is a domain-specific abstraction and not a prime, and carries the case for its domain-specificity in one place.

What is skeletal (could lift toward a cross-domain prime). Strip the programming and one thin relational structure survives: when a host system withholds an abstraction that its use genuinely requires, that abstraction does not vanish — it grows back inside the system as a worse, unacknowledged re-implementation; the only controllable variable is whether it is built deliberately or accretes accidentally. The portable pieces are abstract — a host with a fixed expressive floor, a demand that exceeds it, and the internalization of the shortfall as a defective copy. This is the inner-platform effect (more broadly: systems grow toward the expressivity their environment requires), and it genuinely recurs across substrates — a spreadsheet accreting into a bad database, an org reinventing a worse HR system, a config format sprouting its own conditionals. That inner-platform core is what Greenspun's rule specializes from, not what makes it Greenspun's rule; were an inner_platform_effect prime catalogued, the tenth rule would become its programming-language specialization.

What is domain-bound. Everything that individuates the aphorism is programming furniture. The host is not generic — it is an under-expressive base language (C or Fortran) withholding named features: first-class functions, runtime code construction, macros, homoiconicity, first-class environments, symbolic manipulation, dynamic dispatch, garbage collection. The re-implementation is not generic — it is "an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp," the Lisp-versus-C framing being the whole joke. Its remedies are software-specific (adopt a more expressive base language, embed a real scripting language, prefer composable libraries over framework-internal DSLs), its dual-concept neighborhood (the Lisp tarpit, the Turing tarpit) is programming-language theory, and its worked cases (the accreted rules-engine interpreter; Redis/nginx/Emacs embedding Lua or Emacs Lisp) are all software. Its origin is programmer folklore, numbering-joke and all. The decisive test: remove the languages, programs, and withheld features and there is no Greenspun's rule left — only the bare inner-platform effect, which a spreadsheet or an org chart exhibits with none of the Lisp-and-C vocabulary.

Why this does not clear the prime bar. A prime's vocabulary travels and its transfer is recognition of the same mechanism, not analogy. Greenspun's rule's transfer is bimodal. Within programming-language design it moves as mechanism — the diagnostic (scattered odd subsystems back to one expressivity gap), the quantitative prediction (cost scales with the accreted embedded language, not with feature count), the deliberate-versus-accidental branch, and the threshold boundary all carry intact across PL theory, architecture critique, and the tarpit duals, because each is the same substrate: programs on a base language too narrow for their domain. Beyond software it travels only by analogy: "every spreadsheet contains a bad database," "organizations always reinvent a worse HR system" borrow the vivid shape while renaming the components and dropping the expressivity-gap mechanism. And when the bare cross-domain lesson is wanted — the withheld capability will appear somewhere; choose where it lives and build it deliberately — it is already carried, in more general form, by the inner-platform effect. The cross-domain reach belongs to that parent; "Greenspun's Tenth Rule," as named, carries the Lisp-versus-C framing, the specific withheld language features, and the software remedies that should stay home (and is worth preserving in place as programmer folklore rather than promoted to a prime).

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) — more general patterns this builds on

  • 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

Not to Be Confused With

  • The inner-platform effect (the umbrella it instantiates). The general pattern in which a system, denied an abstraction by its host, grows a worse re-implementation of that abstraction inside itself. Greenspun's rule is its programming-language specialization, keyed to a specific host (an under-expressive base language) and a specific re-implementation ("half of Common Lisp"). Tell: does the diagnosis name the withheld language features (macros, first-class functions, runtime code construction) and prescribe a more expressive base language — Greenspun — or does it generalize across spreadsheets, org charts, and config formats with no Lisp-and-C cargo, in which case the portable content is the parent, treated more fully in the Knowledge Transfer and Structural Core sections?
  • The Lisp tarpit. The dual concept at the opposite end of the expressivity axis: a language so expressive that every project re-implements its own idiosyncratic subset, so power creates fragmentation rather than the accreted-workaround sprawl that under-expressivity creates. Greenspun names the under-expressive end where the missing capability migrates in; the Lisp tarpit names the over-expressive end where surplus capability scatters. Tell: is the complaint that the language withholds capabilities the domain needs (Greenspun) or that it supplies so much that no two projects converge on the same subset (Lisp tarpit)?
  • The Turing tarpit. A language in which the needed power is technically present but practically unusable — everything is possible, nothing is convenient. It differs from Greenspun's case because there the capability is genuinely absent from the base language and must be internalized; in the Turing tarpit the capability exists but is too awkward to reach. Tell: is the feature missing and therefore re-implemented inside the program (Greenspun), or present-but-unwieldy so that no re-implementation is needed, only patience (Turing tarpit)?
  • The second-system effect. Brooks's pattern in which a designer's second system is over-engineered with every feature restrained from the first — a failure of over-ambition, not of an expressivity gap. Greenspun's accreted interpreter is not deliberately designed at all; it accretes as the smallest local workaround at each step. Tell: was the bloat added on purpose by an over-confident designer (second-system effect), or did it assemble itself piecemeal because the language forced each capability to be hand-rolled (Greenspun)?
  • Not-invented-here / reinventing the wheel. The tendency to rebuild an externally available solution rather than adopt it — a sourcing choice about where a component comes from. Greenspun's rule is not about spurning an available library; the missing capability is forced to be internalized because the base language cannot express it, so the re-implementation is unavoidable, not a preference. Tell: could the team simply have adopted an existing external component and chose not to (NIH), or was the capability withheld by the language so that some internalization was mandatory and only its manner was controllable (Greenspun)?
  • An embedded DSL / deliberately embedded scripting language (the contrast case). A real, acknowledged, documented internal language — Redis's Lua, Emacs Lisp — built once and reused. This is not a confusable failure but the deliberate branch Greenspun's rule prescribes as the remedy: the same capability internalized cleanly rather than accreted defectively. Tell: is the internal language built once, tested, documented, and reused (the deliberate branch, the fix), or is it an unrecognized aggregate of runtime string-parsers and reflection hacks that no one admits maintaining (the accidental branch Greenspun diagnoses)?

Neighborhood in Abstraction Space

Greenspun's Tenth Rule sits in a crowded region of the domain-specific corpus (21st percentile for distinctiveness): several abstractions share nearly its structure, so a description that fits it tends to fit its neighbors too.

Family — Software Evolution & Entropy (7 abstractions)

Nearest neighbors

Computed from structural-signature embeddings · 2026-07-12