01
The premise
The web’s gift—and its invoice
The web is an extraordinary environment. Arbitrary, untrusted code arrives on demand, opens on nearly any machine, and is reasonably safe to run.
A product is easy to share. A programmer can get a satisfying visual result in minutes. That makes the web perhaps the easiest place to prototype an application.
It also gives the ecosystem a powerful “worse is better” instinct: make the rough thing available, let it spread, and improve it later. That instinct helped the web win. It also left us with layers of machinery—webpack-era build systems, Flux-era state management, and countless conventions built on top of conventions.
The legitimate invoice for reach
- Different screens and densities
- Mouse, touch, keyboard, and pen
- Accessibility and internationalization
- Unreliable networks and caching limits
- Crawlers, bots, and link previews
- Old browsers and low-end devices
- Media formats and decoding
- Permissions and sandboxing
Some complexity is the honest price of that reach. The mistake is not paying the invoice. The mistake is paying invoices that belong to somebody else.
The browser should provide distribution and capabilities.
Applications should produce experiences.
02
Define complexity
Count behaviors, not files
Complexity is the number of ways a system can behave.
That definition is more useful than counting files, lines, dependencies, or abstractions. The real cost is the number of facts and possible behaviors that must stay coordinated before a change is safe.
Consider a harmless-looking type:
type User = {
is_signed_in: boolean
username: string | null
display_name: string | null
is_subscribed: boolean
is_deleted: boolean
is_banned: boolean
is_vip: boolean
}
possible states, before counting what the strings contain
Those states include combinations the product may never intend: a deleted VIP, a banned subscriber, or a signed-out user with a username. If the type permits those combinations, every consumer must either decide what they mean or improvise accidentally.
Handling every possible state is responsible. Writing 128 conditionals on every route is not. The better move is to remove states that should never exist and encode the intended alternatives once.
type User =
| {
status: "authenticated"
username: string
display_name: string
}
| {
status: "anonymous"
}
The union makes a product decision structural. Consumers no longer need to remember a convention about which combinations are valid. Prefer structures that produce the right behavior over conventions that ask everybody to remember it.
03
Principle 1
Reality sets the price
Every complex system should be able to name the pressure that makes a simpler alternative inadequate.
Then interrogate it. Does that pressure apply to us now? Actually? Could the experience change instead? Could a deeper layer own the guarantee?
Our actual environment
Millions of users.
Media-heavy, real-time creative flows.
A long tail of very large creators.
Roughly seven web engineers with broad, independent ownership.
Those facts justify careful media systems, predictable recovery, and serious performance work. They do not justify architecture designed for a thousand frontend engineers changing one application in parallel. Organizational scalability is a real constraint—but only when it is actually yours.
Keep forced machinery narrow
Server-side rendering is a useful example. Discoverability and link previews require pages that crawlers can understand. Midjourney runs a Bun server for marketing pages and image previews, while the bulk of the application stays client-side.
The constraint is named, its effect is narrow, and the machinery remains proportional to its reason. If the pressure disappears, the machinery should be easy to remove.
Specialize around the product, scale, environment, and team you actually have.
05
Know the bounds
Prefer predictable whole-path behavior
We re-render the whole application on scroll. In some places, on mouse move.
That sounds reckless if average work is the only standard. Our standard is predictable frame behavior across the whole experience, including unusual states, large accounts, custom hit testing, scrolling, and moving interface elements.
A deliberately provocative ruleDo all the work, all the time.
If it is slow, fix it until it is always fast enough.
This is not universal advice. It is one way to prevent rare combinations from secretly becoming much slower because a memoization boundary or incremental path stopped behaving as expected. The complete path exposes the tail instead of optimizing only the friendly path.
Good engineering is not merely producing a good average. It is knowing the bounds.
06
Networking is complex
Make execution order, ownership, and lifetime visible
Websocket reconnection is a global ordering problem. Turn it into a written protocol.
- 01
Preserve a durable resume cursor or sequence.
- 02
Reconnect and subscribe without opening a gap.
- 03
Fetch a sequence-tagged snapshot of running and completed work.
- 04
Restore progress subscriptions.
- 05
Reconcile idempotently and reject stale progress.
A claim such as “we never miss anything” is only defensible when the backend provides the cursor, there is no gap between snapshot and stream, and reconciliation is idempotent. Those preconditions are the engineering.
During a failure, we need to know what ran, in what order, which state it owned, and how long that state was allowed to live. Quantify tolerances, failure, and recovery. Imprecision is sometimes acceptable; unexplained imprecision is not.
07
Principle 4
Abstract machinery, not meaningful decisions
An abstraction becomes dangerous when its interface makes the caller believe the problem has a simpler shape than reality.
A websocket capability may hide timers, retries, and socket APIs. It should not hide the policy for stale data, cascading failure, or reconciliation.
Fonts, DOM, measurement machinery
Geometry, ordering, product placement
Pretext can hide DOM access, font loading, and measurement machinery. It deliberately keeps preparation and layout visible, along with the geometry, ordering, and placement that the product still needs to decide. That is the line: hide mechanism, expose meaning.
Prefer capabilities over frameworks. A focused library or bounded service can solve one difficult boundary without deciding the shape of the entire application. Do not build a website around the way React—or any framework—wants the code organized. Build the experience users need, and go around the framework when the experience requires it.
A screen is a state. The experience lives in the transitions between states.
08
The final ten percent
A prototype asks a question. A product makes a commitment.
The web is unusually good for prototyping. That is a gift, but prototyping and committed product engineering are different concerns.
A prototype proves that something can happen. Engineering makes it keep happening.
Before the prototype becomes a product, name:
- Data
The structures, mutations, and places where state is stored.
- Standards
The performance bounds the application must meet.
- Depth
The guarantees a backend or deeper layer can enforce.
- Edges
The cases the happy prototype never encountered.
- Failure
Every error state, tolerance, and recovery path.
- Ownership
Who owns each decision and how long its state lives.
Engineering begins where multiple reasonable requirements cannot all win. The work is choosing the tradeoff deliberately, then finishing the unglamorous final ten percent that makes the commitment dependable.
09
Looking forward
Let AI pay mechanical complexity
AI lowers the cost of synchronized edits, adapters, boilerplate, migrations, and other mechanical work. That changes some architectural tradeoffs.
If a model can keep explicit code synchronized, an abstraction that existed mainly to reduce typing may no longer be worth the conceptual cost. Two intentionally unfinished experiments follow from that instinct:
One initial request instead of roughly sixteen
Reduce request ordering and partial-loading regimes without ignoring payload size, caching, or time to useful rendering.
One geometry system on controlled creative surfaces
Test whether explicit positioning can replace a split between CSS and JavaScript without sacrificing accessibility or responsive text.
These are experiments, not doctrine.
LLMs can pay mechanical complexity.
They should never conceal conceptual complexity.
Models do not decide state ownership, ordering, tolerances, failure policy, or recovery for us. Those decisions often reveal their consequences years later or only in a small tail of users. Engineers still own the operating model.
10
Principle 0
Know yourself
Γνῶθι σεαυτόν
In engineering terms: be honest about the complexity you actually have to tackle. Understand the product, environment, team, and failures you cannot tolerate. Refuse both imaginary constraints and comforting denial of real ones.
- 01
What constraint, product promise, or intolerable failure forces this complexity?
- 02
Does that pressure apply to us now?
- 03
Can we change the experience instead?
- 04
Can a deeper layer own the guarantee?
- 05
If it is unavoidable, how small can its boundary be?
Make time, memory, and order explicit.
Care about what happens beneath the finger.
Estimate, define tolerances, and recover in an imperfect world.
Borrow the standards that travel: visible order, bounded resources, back-of-the-envelope math, explicit tolerances, deliberate recovery, and care in the transitions where the experience actually happens.
Lowering complexity is an organizational technology. It lets a small team own big pieces, work independently, deploy calmly, and keep systems understandable for years.
Build the experience.
Name the pressure.
Pay only what it costs.
Sources and credits
- “Worse Is Better”, Richard P. Gabriel.
- Raptor 1 photograph by Brandon De Young, licensed CC BY-SA 4.0, via Wikimedia Commons.
- The Pretext example comes from Cheng Lou’s work.
- Architecture and networking examples are adapted from practices discussed by the Midjourney web team.