Lesson16 min

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.

ComponentWhat it isThe one-line justification
Load balancerSpreads requests across servers, health-checks them"Lets me run many stateless app servers and survive one dying"
API gatewayThe front door: routing, auth, rate limiting"One place for cross-cutting concerns instead of per-service"
App servicesStateless servers with the business logic"Stateless so any server handles any request — horizontal scaling"
DatabaseSource 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:

  1. Name the problem precisely. "Two users can pass the availability check for the same seat before either writes."
  2. 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)."
  3. 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."
  4. 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.