Case study
System Design Playground
Sketch a system, send traffic, watch it break, then fix it. A deterministic simulator in Go with a hand-drawn React canvas.
- Status
- Open source, MIT
- Engine
- Go, deterministic discrete-event
- Components
- 13 building blocks
- Challenges
- 22 guided scenarios
Problem
System design is usually taught with static boxes and arrows. You never see a queue fill, a connection pool run dry or a retry storm build, which is exactly what interviews and production incidents are about.
Context
I wanted a place to build a design and then stress it: send traffic, crash a node, and learn from the failure instead of memorising a diagram.
Constraints
- Results must be reproducible, so the same design and seed always give the same outcome.
- Live edits must apply to a running simulation.
- Shared designs come from untrusted users, so they must be sanitised.
- It must be fast enough to feel interactive.
Architecture
A browser edits the diagram and streams it to a Go simulation server over a WebSocket; the server streams stats and request particles back.
Decisions
Why Go for the engine?
A tight event loop with predictable performance. A 30 s run of a 1,000 rps, 20-node design takes about 90 ms, so the simulation can run many times faster than real time.
Why deterministic?
Same diagram and seed give the same result, which makes challenges fair, tests reliable and bugs reproducible.
Why run the simulation on the server?
One authoritative engine, no duplicated logic in the browser, and the UI stays a thin editor and renderer.
Why scenarios as data with tests?
A scenario that does not fail at the start, or does not pass with its reference solution, is caught by the test suite instead of by a learner.
How is it validated?
The engine is checked against queueing theory, and covered by engine, scenario-calibration, API and WebSocket tests.
Trade-offs
- A request-flow and capacity model rather than a full distributed-systems emulator.
- Stateful server sessions in exchange for live editing.
- Anonymous sharing by capability link rather than accounts.
Implementation
- Go backend: engine (no I/O, unit tested), scenarios and an API with REST and WebSocket.
- React, TypeScript, Vite and zustand frontend; roughjs canvas; keyboard and touch support with a list-based fallback for the canvas.
- Docker image and Makefile for one-command runs.
Outcome
- Components cover load balancers, caches, replicas, queues, workers, CDN, rate limiters, shards and more.
- 22 challenges, request-trace waterfalls, chaos injection and shareable designs.
Lessons
- Determinism is a feature: it makes simulation testable.
- Treat user input as hostile even in a toy: sanitise diagrams on the server.
What I would change today
- From the project's own gap analysis: it models request flow and capacity well, but not data-flow questions such as feeds, chat or video pipelines. Those need new building blocks (fan-out, pub/sub, blob storage).