A complex system that works is invariably found to have evolved from a simple system that worked. The converse bites harder: a complex system designed from scratch never works on the first try and can't be patched into working — you must fall back to a simple system that runs and let it grow.
Why it's non-trivial: ① It is the fundamental victory of evolution over design. Complexity can't be assembled in one shot; it can only be grown step by step — and every step must preserve the invariant "the system still runs." This is isomorphic to biological evolution: no complex organ was designed in one go; the eye accreted from a light-sensitive patch, and every intermediate form had to be useful. ② It is of a piece with complex adaptive systems — complexity is an emergent product, not a blueprint product. The most dangerous thing about a perfect architecture diagram is precisely that it skips the "simple intermediate state that runs." ③ Direct corollary: the big-bang rewrite almost always fails, because it asks a complex system that has never run to run all at once.
In practice: translate every urge to "build the complex system directly" into "first find the minimal version that runs, then let it grow." Diagnostic question: after this step, does the system still run? The moment a step makes it stop running, you're violating Gall's Law.
The Internet wasn't designed — it evolved from a few-node, simple-but-working ARPANET, staying connected and usable at every step until it grew into a global network. Unix and the World Wide Web are the same: a simple, working kernel grown incrementally. None of them was "fully designed before launch."
① Engineering: when building an AI-agent system, don't start by designing a ten-agent orchestration framework — get a single agent working end-to-end on a real task first, then add the second. Working first, complex later. ② Home: want a "perfect schedule system" for the family? Run a single shared list for two weeks; once it works, add automation. ③ Anti-pattern alarm: when you catch yourself carefully designing complex structure "for future use," it probably can't even pass step one. Workingness is the one thing you can't buy on credit.
Be conservative in what you send, be liberal in what you accept. This principle let the early Internet stitch countless different implementations into one interoperable whole: you emit strictly to spec yet tolerate the other side's small flaws, so collaboration doesn't collapse over a single formatting deviation.
Why it's non-trivial: ① Its blessing and its curse are the same thing. Liberal acceptance brings interoperability, but it also lets the spec slowly rot — every implementation accepts slightly different input until, over time, nobody can say what the "correct" format even is, and bugs become de-facto standards (precisely the seedbed for the next card, Hyrum's Law). ② So modern protocol design (HTTP/2, QUIC, many new data formats) swings toward strictness: better to reject early than let ambiguity settle into unrecoverable technical debt. Tolerance borrows "today's compatibility" against "tomorrow's chaos." ③ The balance depends on the system's shape: open ecosystems (maximize interop) lean liberal; closed cores meant to evolve for years lean strict.
In practice: design every interface in two halves — accept the dirty data of the real world as liberally as you can at the inbound edge, but emit strictly to spec at the outbound edge (downstream depends on your output). The key discipline: accepting liberally is not the same as propagating liberally — normalize dirty input into a clean internal representation right at the boundary; never let tolerance seep into the core.
Early browsers' liberal parsing of malformed HTML (quirks mode) let the web explode — anyone's hand-written bad markup still rendered. The cost was decades of rendering inconsistency, and HTML5 having to spend enormous effort precisely standardizing "fault-tolerant parsing" after the fact. Tolerance ignited the ecosystem and left a spec debt behind.
① The interface to an LLM: accept liberally at the inbound edge (users' natural language is endlessly varied — take it in and normalize it), but the output schema you hand downstream must be strict (fixed JSON fields, fixed types), or downstream descends into chaos. ② Communication: be liberal about how others express themselves (don't nitpick wording), but precise in your own outward commitments — the engineering principle, isomorphic in collaboration. ③ Beware the dark side: the more you "accept anything," the less the other side self-corrects, until you're forced to forever support everyone's bad habits.
With a sufficient number of users of an interface, what you "promised" in the contract no longer matters — every observable behavior of your system, documented or not, will be depended on by somebody. The wording of an error message, the iteration order of a hash, a response that got 10 ms faster — anything observable becomes an implicit interface in someone's code.
Why it's non-trivial: ① It is the death of the encapsulation myth. In theory a private implementation is free to change; in reality, with enough observers, any behavior becomes a dependency — there is no truly leak-proof abstraction. ② It is isomorphic to the observer effect: once a behavior is observed, it's pinned. The implementation freedom you think you own shrinks inversely with your user count. ③ At depth it is the emergence of an "implicit contract" — the collective behavior of thousands of dependents spontaneously forms a real contract far larger than your docs, and one you can't recall. ④ Corollary: to preserve freedom to change, either actively hide behavior (inject randomness, deliberately scramble ordering so nobody can depend on it) or lock semantics early, while users are few.
In practice: before shipping an interface, ask — which behaviors I never promised but that are observable are quietly becoming contract? For dimensions you truly want to keep free, inject controlled jitter: "if you depend on this, it will change on you."
The Linux kernel's iron rule, "never break userspace" — because any system-call behavior is depended on by some program, even when it was originally a bug. Windows long preserving known defects for old-software compatibility is the same. This law was distilled by Google's engineers from countless incidents of "changed an unpromised detail and broke downstream."
① Platforms/libraries: once your internal library is used by dozens of teams, you can't even touch log formats or error-message text — each could be someone's parsing anchor; to keep room to evolve, set the rules while users are still few. ② AI prompt engineering: a prompt template depended on by many downstream flows can break a pipeline silently if you change one word or reorder the output — the "observable behaviors" of an LLM's output are all implicit contract. ③ Parenting: implicit promises you set unintentionally (a story every night, the park every weekend) get treated by a child as an inviolable contract — an observable regularity is a promise, even one you never said aloud.
Software gets slower faster than hardware gets faster. Moore's dividend doesn't turn into a faster experience — it's eaten by layers of abstraction, bloated dependencies, and the "the machine is fast enough anyway" mindset (the industry quips, "Andy giveth, and Bill taketh away").
Why it's non-trivial: ① At root this is an incentive/economics problem, not a technical one: the hardware dividend is spent by developers on development speed (more abstraction layers, more third-party dependencies, easier frameworks) rather than returned to users. Performance is actively consumed; it never improves on its own. ② It is isomorphic to entropy — software naturally trends toward bloat, and the "entropy" of complexity only rises unless you continuously inject counter-energy (performance budgets, periodic slimming). ③ It echoes Parkinson's Law: available resources get filled by the workload; give it more memory and CPU and the software bloats to consume them. ④ So "performance discipline" must be an active, institutionalized swim against the current, not a hope that hardware will bail you out.
In practice: set a "performance budget" on critical paths (e.g., cold start < X s, first paint < Y ms) and guard it as a test that can fail — the budget is the counter-energy you inject against entropy. Every time you add an abstraction layer or a dependency, ask first: is the development convenience worth the hardware dividend it eats.
Launch an Electron-based chat or editor app today, and its memory and startup time would have been enough to run an entire 1990s operating system with room to spare. Average web-page weight bloats year over year; no matter how fast the hardware, it can't claw back those seconds of first paint — compute rose by orders of magnitude, the experience barely moved.
① The AI-era Wirth's Law: the compute dividend of stronger models is being eaten by ever-longer agent call chains, layer upon layer of RAG retrieval, and redundant re-prompting — the model got an order of magnitude faster, end-to-end experience barely did. ② Personal workflow: the stronger the tools, the more bloated the process you pile up (ten plugins, five layers of automation), and the net efficiency gain is canceled by the process's own overhead. ③ Counter-discipline: set a "latency/step budget" for your own AI workflow too, and make every added step prove its returned value exceeds the speed it eats. Speed doesn't come back on its own — you have to reclaim it.