High-Level Architecture & Data-Flow
Start from the minimal skeleton, justify every added box against a requirement, label arrows, and prove the design by tracing one request end to end.
Draw the system, then prove it works
The high-level design phase is where you draw the system and prove it works by tracing one concrete request through it. Two failure modes bracket this phase. Some candidates draw a beautiful diagram with 15 boxes and never show how a single request flows, so nobody knows if it actually works. Others draw so little that the design is ambiguous. The fix for both is the same discipline: start with the simplest set of boxes that satisfies the requirements, then evolve it by introducing each new component with an explicit justification, and finally walk one request end to end.
Start simple
Almost every system begins as the same skeleton:
Stage 1 of 3: Start with the simplest set of boxes that can serve a request: clients reach stateless app servers through a load balancer.
That is a complete, working system for a huge class of problems. Only now do you add components, and only with a reason tied to a requirement:
- A gateway / reverse proxy (Envoy, NGINX) when you need auth, rate limiting, or TLS termination in one place.
- A cache (Redis) when reads dominate and repeat, to cut datastore load and tail latency.
- A message queue (Kafka, SQS) when work is async, spiky, or must survive a consumer being down.
- A CDN (CloudFront) when you serve static or geographically distributed content.
- An object store (S3) for large blobs (images, video, files) that do not belong in a row.
- A search index (Elasticsearch) when you need full-text or faceted queries the primary store cannot serve.
The discipline is that you say why each box exists. "I am adding Redis here because reads are 10x writes and the same hot keys repeat, so caching cuts p99 and datastore QPS." A box without a justification is the single most common wrong turn: adding Kafka or sharding you cannot yet defend makes you look like you are pattern-matching, not designing.
Each situation justifies exactly one added box. Which component earns its place on the diagram?
Trace a concrete request
Now the part that separates a strong answer: pick one real operation and follow it through every box, both the write path and the read or delivery path. For a chat message the write path is client to gateway to chat service, persist to the message store, enqueue for delivery. The read path is the recipient's connection receiving a push, or the recipient client pulling on reconnect. Tracing forces you to notice gaps: where is the message stored before delivery, what happens if the recipient is offline, how does the sender get an ack.
Interview nuance: label your arrows. An arrow should carry what flows and how: "WebSocket frame,"
"gRPC call," "async event on Kafka topic messages." Unlabeled arrows hide the exact decisions
interviewers probe. Group boxes into tiers (edge, service, data) so the diagram stays legible as it
grows.
Interview nuance: explicitly point at where each functional requirement is satisfied. "Requirement 1, send a message, happens on this write path; requirement 2, delivery, happens on this arrow." This is how you prove completeness before you move to deep dives, and it is what lets you honestly say "I have a working design now."
Recap: start with the minimal client-LB-app-DB-cache skeleton, add each component only with a requirement-tied justification, label arrows with data and protocol, and prove the design by tracing one concrete request through both its write and its read or delivery path.
Apply
Your turn
The task this lesson builds to.
Draw the boxes-and-arrows for a chat app and narrate a single message's full path from sender client to recipient device, including the write and the delivery.
Think about
- What is the simplest set of components that satisfies the requirements?
- Can you trace both the write path and the read/delivery path concretely?
- Where is each functional requirement satisfied in the picture?
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Draw the boxes-and-arrows for Uber-style ride matching and trace one request from a rider tapping 'request ride' to a nearby driver's phone ringing, at 1M concurrent riders. Handle both the write (request) and the delivery (dispatch to driver) paths and show where geospatial matching lives.
Think about
- Where do continuous driver location updates land, and why can that not be the primary database?
- What happens between 'trip row created' and 'driver phone rings', box by box?
- How does the design handle a driver declining or timing out on the offer?