Scott Bain’s Emergent Design names something most developers have watched happen whether or not they wanted it to: a design meets use and changes.
Plenty of teams still plan the structure first and fill it in, and the structure still mutates once it touches the real world; the difference is only where the mutation happens1. Bain’s argument, and XP’s before him, is to move that contact as early as possible: write a test, make it pass, refactor, and let the structure arrive while it’s cheap to change - thus it emerges from how it's used.
The tests hold the design while it’s forming. They’re the center the code is built around, and the code fulfills its design through them, with the feedback arriving in seconds instead of after deployment. YAGNI is the rule that keeps it honest: don’t build for requirements you don’t have, because the ones you do have are about to reshape the code anyway.
That’s design emerging inside a boundary, by the people who own it, with the feedback loop deliberately pulled inward. There’s an older version of the same idea that works across boundaries, by people who don’t own anything, and it was running long before anyone said “emergent design” in a software context2. Same principle, bigger scope, a different kind of center, and a feedback loop nobody controls at all.
Here’s a simple example: a JAR file is a zip file. The decision was based on convenience: zip existed, it compressed, it had a directory, nobody wanted to invent an archive format for a language runtime3. Out of that convenience fell fat jars, WARs, EARs, shading, classpath scanning, and every build tool and IDE that reads an archive without having to start a JVM. None of that was planned. All of it was cheap, because the first use was narrow enough that the second use cost almost nothing.
In Bain’s version the center is the tests. In this version the center is the substrate, and the thing that emerges isn’t structure but possible uses outside of the original intent: a surface someone built for one caller, and a second caller nobody expected. You designed a thing, intending it for a specific purpose, only to find that it was incredibly useful for things you’d never thought of.
There are three conditions for this. First, the substrate is narrow: it does one thing.
Second, it’s legible from outside: you can see what goes in and what comes out without reading the implementation.
Lastly, it’s permitted. Nobody fences off the second caller, by policy or by over-specifying the interface to its first use.
Narrow, legible, permitted. Remove any one and emergence is far more difficult.
Unix is a classic example: the substrate was deliberate. File descriptors, text streams, the shell as glue: those were decisions. Pipes were not discovered; McIlroy argued for them for years before Thompson put them in. It was engineered on purpose, and engineered small.
What emerged was the way the substrate was used. Nobody designed ssh as a transport layer, and now git, rsync, scp, port forwarding, and (what feels like) half of every deploy script ride on it, because it was a text pipe with authentication already solved. Sockets being file descriptors was a choice made for one reason, and inetd and later systemd socket activation fell out of it. xargs exists because the pipe model needed a bridge to argv and somebody noticed… and threw it together. The shell itself is the clearest example: a command interpreter that turned out to be a programming language, not because anyone set out to write one, but because the primitives composed and people kept, well, composing.
That a small substrate can produce a large universe isn’t the surprise. Conway’s Life does it with four rules and nobody steering, and anyone who’s written a shell one-liner has felt it. The surprise is that the universe has an edge, and the edge is exactly the substrate’s. Everything inside the primitives composes; one step outside them, nothing does4.
Emergence runs exactly as far as the substrate’s primitives and no further. The shell universe covers everything that reads and writes text streams. The moment a problem needs one thing outside that, like, say, an authenticated websocket, the whole composable ecosystem goes quiet ... and you write a tool. The edge is sharp, and it has a consequence most people get backwards: a small addition to the substrate is worth more than a large addition to any one tool. jq is the clean example. Before jq, JSON was outside the pipeline; you could see it but you couldn’t compose with it. After jq, every tool that emitted JSON became a shell citizen, retroactively, without any of them changing. One primitive, and the reach of the whole ecosystem moved5.
None of this is a property of Unix. It’s a property of substrates, and the desktop proves it out even further, because the desktop is the environment least like Unix in spirit. GUI applications are sealed by default; their function lives behind buttons and buttons don’t compose easily6. Launchers changed that. Spotlight on its own is search, but Alfred workflows and Raycast extensions turned “type a thing, get a thing” into a composition point, and the universe that grew on it was never designed by Alfred or Raycast or Volant. Clipboard history feeds snippet expansion feeds a URL scheme feeds an application someone else wrote. dmenu on Linux makes the point with almost no code: it’s a menu over stdin, a pipe with a window on it.
The primitives behind that are the same kind of small addition jq was. URL schemes, AppleScript dictionaries, Shortcuts actions, x-callback-url. Each application that exposes a primitive becomes a citizen of the whole ecosystem, and the people assembling workflows out of them aren’t designing anything but they're making connections. They’re looking at what’s reachable and noticing a fit.
Lévi-Strauss called that figure the bricoleur, someone who works with what’s in the bag because that’s what’s there, compared to the engineer who specifies and procures. Every one of these universes is bricolage over a substrate someone else engineered, and the substrate is the only part that was ever what we’d call “engineered.”
That’s where the two versions of emergent design - internal and external - meet, and why Bain’s rule holds at both scales. YAGNI says don’t build for requirements you don’t have, because you ain’t gonna need it. The cross-boundary version is the same sentence: don’t design for callers you don’t have.
The broad, general, forward-looking interface built for unknown future uses is exactly the speculative abstraction YAGNI forbids, and it fails the same way, because nobody reuses an interface they’d have to understand first. If you want a second caller, you build the narrowest surface that does today’s job, you make it legible from outside, and you don’t lock it to the first caller when the second one shows up. Inside a codebase the tests hold the center. Across codebases the substrate does. In both cases the design arrives when something uses it, and not before.
And it’s awesome.
-
Well, this difference is also reflected in the work this creates for the team, because mutating a design after deployment is a much heavier workload than letting the design come from how it's used in testing. IMO, of course.
↩ -
I'm pretty sure the people who came up with "emergent design" as a term were seen as irreverent hippies by the old guard who were busy waving about punch cards and pocket protectors.
↩ -
They left "let's do emergent design badly" for Linux package managers to exploit. UDS and zarf are bespoke formats, and so they don't get to play. They need tools, and until they have them, they're a pain.
↩ -
In other words, Conway's system is bounded: cells have specific input and output states, but they can't, say, open a socket.
↩ -
And now there're things like nushell, which treat structured data as a lingua franca inside the shell itself, which is kinda neat. nushell has its limitations, but it's a great idea.
↩ -
Of course, then one imagines things like robotjs and similar projects and libraries that sort of do add composition to GUIs, mostly for testing... maybe. It's turtles all the way down, who knew?
↩
No comments yet.
Sign in to comment