12 small, focused projects to learn how to build a production-grade real-time orderbook UI — the kind that powers perpetuals exchanges like Hyperliquid, dYdX, and Aevo.
Live demo (WIP): coming after Kata 12 Twitter thread: coming after Kata 3
Most React advice doesn't apply to trading UIs.
A trading surface receives 5,000–10,000 state mutations per second over WebSocket and must render them at 60fps without dropping frames, without showing stale prices, without silently rounding numbers, and without locking up when the network blips.
That's not a "React performance" problem. It's a distributed systems UI problem — and the patterns are different:
- State lives outside React (plain class) — never inside
useState - Updates are rAF-coalesced (decouple input rate from render rate)
- The orderbook lives in a Web Worker (main thread stays idle)
- React subscribes via
useSyncExternalStore(tearing-free reads) - All prices are
decimal.jsend-to-end (neverparseFloat) - WebSocket has sequence validation + snapshot/replay resync
- Rows are virtualized + depth bars update via CSS variables, not React
This repo builds each of those primitives in isolation, then composes them into a full orderbook. One kata = one concept = one working artifact.
| # | Kata | Concept | Status |
|---|---|---|---|
| 1 | WebSocket echo | Connect, subscribe, lifecycle, type-narrow messages | Done |
| 2 | Reconnect manager | Exponential backoff + jitter, AbortController | In progress |
| 3 | Sequence gap detector | Track U/u from depth stream, log gaps |
Planned |
| 4 | Sorted side data structure | RBTree + decimal-safe prices | Planned |
| 5 | Pure applyDelta function |
Immutable book updates, fully tested | Planned |
| 6 | Snapshot + replay buffer | THE algorithm of market data engineering | Planned |
| 7 | rAF batcher utility | Decouple WS rate from render rate | Planned |
| 8 | Web Worker round-trip | Offload parsing + book to background thread | Planned |
| 9 | useSyncExternalStore counter |
External state, no React state | Planned |
| 10 | Virtualized list | Render 10k rows with ~30 in DOM | Planned |
| 11 | CSS-variable depth bar | Update DOM without re-rendering React | Planned |
| 12 | Flash-on-change row | CSS animation triggered without useState |
Planned |
| ★ | Full orderbook + trades tape | Wiring all 12 primitives together | Planned |
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 16 (App Router) | RSC for shell, client islands for the book |
| Language | TypeScript (strict) | Numeric correctness is brutal without types |
| Styling | Tailwind CSS | No runtime cost, fast iteration |
| Numbers | decimal.js |
Never parseFloat for prices |
| Sorted maps | bintrees (RBTree) |
O(log n) insert/delete, ordered iteration |
| External store | React 18 useSyncExternalStore |
Tearing-free at high update rates |
| Virtualization | @tanstack/react-virtual |
Industry standard |
| Tests | Vitest | Fast, native ESM |
| Data source | Hyperliquid public WS | Real perp DEX data, free, no auth |
pnpm install
pnpm dev
````md
# Real-Time Trading UI Katas
A collection of focused frontend katas for learning how modern trading interfaces work — from WebSocket streams and reconnect logic to orderbook rendering and decimal-safe market data handling.
Each kata is implemented as a separate route under `/app`.
---
## Routes
Visit:
```bash
http://localhost:3000/01-ws-echo
http://localhost:3000/02-reconnectConnects to Hyperliquid's public WebSocket, subscribes to ETH trades, and renders the latest 20 trades with a connection-status indicator.
-
WebSocket lifecycle management:
onopenonmessageonerroronclose
-
React Strict Mode double-mount behavior makes cleanup logic essential. Failing to close sockets correctly can create duplicate connections and stale listeners.
-
Hyperliquid sends prices and sizes as strings to preserve decimal precision.
Never blindly parse market data into floating-point numbers.
This was my first real lesson in decimal safety for trading systems.
-
The first message after subscribing is a
subscriptionResponse, not actual trade data.Always validate message type/channel before consuming stream data.
Reflection added on completion.
I'm a frontend engineer building expertise in real-time trading UIs and DeFi infrastructure.
This repository is my public learning journey while exploring:
- trading system UX
- WebSocket architecture
- realtime rendering
- exchange interfaces
- market data handling
If you're building:
- a perpetuals exchange
- DEX aggregator
- trading dashboard
- realtime DeFi infrastructure
and want to talk, my DMs are open on Twitter.
MIT