High-Level Design & Deep Dives
Assembling the sensible baseline (LB, services, DB, cache, queue), justifying each component, and choosing 2-3 deep dives that show mid-level judgment.
The high-level design: where mid-level interviews are won or lost
This is the 10-15 minute phase where you draw boxes and arrows — and it is the phase most mid-level failures trace back to, because the bar is unambiguous: a complete system that satisfies every functional requirement. An 80%-drawn diagram with one exquisitely optimized corner scores worse than a plain, finished design.
The method that keeps you on track: build the architecture one API endpoint at a time. Take the first endpoint and walk it through the system out loud — "Let me walk POST /tweets through end to end: client → load balancer → tweet service → it writes a row to the tweets table" — narrating the data flow and what state changes at each hop. Then the next endpoint. When every endpoint has a working path, the phase is done: "At this point we have a working system that satisfies all three functional requirements — now let's make it meet the non-functional ones."
The baseline components
Most designs assemble from the same small kit. What matters is not drawing them — it is being able to justify each one, because at mid-level nothing you draw is taken for granted. If you add an API gateway, expect "what does that do, and why do we need it?" Being unable to explain your own boxes is a level-defining failure.
| Component | What it is | The one-line justification |
|---|---|---|
| Load balancer | Spreads requests across servers, health-checks them | "Lets me run many stateless app servers and survive one dying" |
| API gateway | The front door: routing, auth, rate limiting | "One place for cross-cutting concerns instead of per-service" |
| App services | Stateless servers with the business logic | "Stateless so any server handles any request — horizontal scaling" |
| Database | Source of truth (default: Postgres) | "Relational default; I'll justify NoSQL only if access patterns demand it" |
| Cache (Redis) | Hot data in memory, ~1ms vs 20-50ms | "Meets the latency target on the read-heavy path" |
| Queue (Kafka/SQS) | Buffers work between services | "Absorbs write spikes; decouples slow work from the request path" |
Two habits while drawing. Jot key schema columns next to each database ("tweet_id, author_id, content, created_at") — it grounds the boxes in real data without a full schema detour. And justify the database choice with one concrete reason tied to access patterns — "I'll use NoSQL because scale" is a classic mid-level failure line.
Flag bottlenecks; do not fix them yet
While walking endpoints you will see problems coming: the naive feed query joins every followed user's tweets; the seat-booking write can race. Resist premature optimization. Say it and move on: "I know this naive feed query won't scale — flagging it now, I'll fix it in the deep dives." You get credit for seeing it, you build your deep-dive agenda, and you protect the finish. Fixing it inline is how candidates end up at minute 40 with no wrap-up and half the endpoints unwalked.
Deep dives: hardening the design
With a complete baseline, return to two lists you already made: the non-functional requirements and the flagged bottlenecks. Deep dives are where you close them, one at a time — roughly 10 minutes, usually 2-3 dives.
At mid-level, expect the interviewer to pick the topics — "how do you prevent double-booking?" — and know that being steered is normal, not a bad sign. Your job is to respond with structure. The reliable shape for every dive:
- Name the problem precisely. "Two users can pass the availability check for the same seat before either writes."
- Offer two options. "A status column with an expiry cron job, or a Redis lock with a TTL (a time-to-live, so an abandoned lock frees itself)."
- State the trade-off. "The status column is simpler and durable; the Redis lock releases instantly and keeps hold-state out of the main table, but adds a dependency."
- Pick one and justify it against the requirements. "Given our strong-consistency requirement on bookings, I'd take the Redis lock with a 10-minute TTL."
That problem → 2 options → trade-off → choice pattern is the single highest-value habit in this entire module. It works on every dive: cache invalidation, feed fanout, queue backpressure, failover.
Choosing dives that show mid-level judgment
If you get any say in topics, pick dives that (a) trace directly to a non-functional requirement you stated, and (b) you can land concretely. The classics:
- Latency: "Our <500ms target isn't met by the naive read path — let me add a cache, and here's how invalidation works" (cache-aside, TTL, what happens on write).
- Consistency: the double-booking / double-charge race, ending in a concrete mechanism (a lock, a version check, an idempotency key).
- Scale on the hot path: read replicas before sharding; a queue in front of a write spike; "what happens when the queue backs up? — we'd see queue-depth metrics and scale consumers."
Landing the "good" tier answer on each is the bar; reaching the "great" tier (the Redis TTL lock, the hybrid fanout) is upside. Failure modes count too: "what if Redis dies?" deserves a real answer (fail-open vs fail-closed, fallback to the DB), not "it retries."
Wrap-up: land the plane
Reserve the last 2-3 minutes, no matter what. Recap against the requirements from step 1 — "posting works via the write path, the feed meets 500ms via the precomputed cache, and we prioritized availability throughout" — then name what you would tackle next with more time ("the celebrity fanout edge case, multi-region failover"), and invite questions. Never end mid-tangent. Ending on a completed, requirement-checked system is exactly the "delivered a working design" bar that mid-level candidates most often miss — and the easiest one to hit on purpose.