Experiments
Each experiment in the distributed-systems-lab solves a different real-world problem. The language is chosen based on what fits: TypeScript for event-driven and API-level patterns, Go for concurrency and systems-level work.
Active experiments
Section titled “Active experiments”Rate Limiter (TypeScript)
Section titled “Rate Limiter (TypeScript)”Problem: Your API is getting hammered. You need to protect it without blocking legitimate users.
What it builds: Two rate limiting algorithms (token bucket and sliding window) behind a switchable middleware. Includes load test scripts that send burst and sustained traffic so you can watch the algorithms behave differently under the same load.
What’s interesting: The burst test is where the strategies diverge. Token bucket allows the first 10 requests instantly (the bucket was full), then throttles. Sliding window is stricter because it accounts for the previous window’s traffic. Same total limit, different behavior under spikes.
Try it:
cd experiments/ts && npm installnpm run rate-limiter:devnpm run rate-limiter:burst # 50 concurrent requestsnpm run rate-limiter:sustained # 3 req/sec for 30 secondsEvent-Driven Order Pipeline (TypeScript)
Section titled “Event-Driven Order Pipeline (TypeScript)”Problem: A customer places an order. Payment, inventory, shipping, and notification need to happen in sequence. What happens when shipping fails after payment already went through?
What it builds: A saga orchestrator that runs four stages forward and compensates in reverse on failure. Payment gets charged, inventory gets reserved, then shipping fails. The orchestrator refunds the payment and releases the inventory hold, in the correct order.
What’s interesting: The compensation order matters. You cancel the shipment before releasing inventory (otherwise the carrier might pick up unreserved items). You release inventory before refunding payment (otherwise you have unreserved items paid for by nobody). Getting this wrong creates real data inconsistencies.
Try it:
npm run order-pipeline:devnpm run order-pipeline:happy # everything succeedsnpm run order-pipeline:fail:shipping # triggers compensation chainnpm run order-pipeline:concurrent # 5 orders, mixed failuresConcurrent File Processor (Go)
Section titled “Concurrent File Processor (Go)”Problem: You have thousands of files to process. One at a time takes hours. You need parallel processing with backpressure so you don’t overload the system.
What it builds: A worker pool using goroutines and channels. A configurable number of workers pull tasks from a bounded channel, process them, and send results through another channel. Backpressure happens naturally: when the task channel is full, the producer blocks.
What’s interesting: Run the same workload with 1 worker vs 8 workers. One worker takes ~10 seconds. Eight workers take ~1.4 seconds. That’s real CPU parallelism, not event loop tricks. Press Ctrl+C during a run to see graceful shutdown: workers finish their current task before exiting.
Try it:
cd experiments/gogo run ./cmd/worker-pool -workers 1 -tasks 50 # sequential: ~10sgo run ./cmd/worker-pool -workers 8 -tasks 50 # parallel: ~1.4sgo run ./cmd/worker-pool -workers 4 -buffer 1 # tight backpressurePlanned experiments
Section titled “Planned experiments”Health Check & Service Registry (Go)
Section titled “Health Check & Service Registry (Go)”Problem: How do microservices discover each other and know when one goes down?
Services register themselves, send periodic heartbeats, and get deregistered when they stop responding. This is the problem that Consul, etcd, and Kubernetes service discovery solve. Building a tiny version of it reveals why those tools exist.
Theory and notes
Section titled “Theory and notes”The conceptual foundation for these experiments lives in system-design-notes, with structured articles on caching, queues, consistency, retries, and more. Two of those concepts (idempotency and cache stampedes) have their own runnable experiments and interactive visualizations.