Skip to content

Senior Engineering Interview Handbook / Chapter 79

High-Level Architecture and Data Flow

A senior system-design interview chapter that follows a ticket purchase through ingress, ownership boundaries, canonical state, payment, durable events, search, caches, workers, analytics, and failure pressure.

The arrows are the architecture

A buyer chooses a seat in a ticket marketplace and presses Confirm. What must happen before the page may truthfully say that the ticket is theirs?

The first diagram is often a collection of familiar boxes: client, gateway, services, cache, database, queue, workers. It looks plausible because every component belongs in some real system. Yet it may conceal the answer to the question. Which boundary decides that the seat is available? When does a hold become a sale? Can the payment provider make the local order true? What if the provider accepts the charge and the database write fails?

High-level architecture begins when the arrows acquire meaning. A request crosses a boundary with particular data; one owner is allowed to make a fact true; some work must finish before the response; other work may lag and must eventually be repaired. The diagram is the compact record of those decisions.

Establish the truth of the purchase

Before drawing, say what success means:

The buyer receives a confirmed order only after a valid hold and payment authorization. Repeating the request does not sell the seat twice or create a second charge. Email, search, seller analytics, and recommendations may catch up later.

That statement gives the diagram a spine. It separates the synchronous truth from deferred consequences and identifies the two dangerous boundaries: scarce inventory and an external payment system.

A useful scratchpad takes six short lines, not a matrix:

  • the buyer starts the flow;
  • a valid hold and one accepted payment are required for confirmation;
  • the purchase boundary decides whether an order can be confirmed;
  • inventory and order records are canonical for the sale;
  • notification, discovery, and analytics may lag;
  • a hot event or a slow payment provider can break the path.

This is enough to begin. Do not decide that the system needs five microservices merely because the product has five nouns.

Draw the synchronous path first

Put the user-visible path across the page and narrate it left to right:

buyer
  -> edge/gateway
  -> purchase boundary
       -> inventory and order store
       -> payment adapter -> payment provider
  -> confirmed or pending response

At the edge, authenticate the buyer, reject malformed requests, apply a rate limit, and pass trusted identity context inward. The purchase boundary loads the hold from canonical state and checks its owner, seat, price, and expiry. An idempotency key identifies this purchase attempt across client retries.

The payment provider is synchronous here for a product reason: the system must not call an unpaid order confirmed. But the network call and the local commit cannot form one ordinary database transaction. Record the attempt, authorize through a narrow payment adapter, then confirm the order and move the still-valid held seat to sold in an idempotent local transaction. If the hold expired during authorization, or authorization succeeds but the local commit does not, reconciliation must find the orphaned authorization and either complete or reverse it. A timeout may require a pending response; blindly retrying the charge is not a recovery plan.

This path already answers questions that a component inventory cannot. The gateway does not own a seat. The provider does not own an order. The response distinguishes confirmed truth from unresolved work. Each arrow carries an operation rather than merely connecting two logos.

It also reveals a design choice worth stating aloud. The first version can keep seat state and order confirmation in one purchase boundary and one transactional store. Splitting inventory and orders into separately deployed services would create a distributed correctness problem. Make that split only when ownership, scaling, deployment, or failure isolation repays the cost.

Give browsing a different path

Buyers need discovery as well as checkout, but discovery need not carry the same guarantees:

search:
buyer -> edge -> discovery API -> search index -> response

event detail:
buyer -> edge -> discovery API -> event-detail cache
                               -> canonical listing on cache miss -> response

reserve:
buyer -> edge -> purchase boundary -> canonical inventory -> hold response

The search index is a derived view of public events and listings. The cache accelerates popular event details. Both may be stale for a stated interval; neither may promise that a seat is still available. The reserve operation returns to canonical inventory and makes the scarce decision there.

Label those facts on the drawing. Write “derived; may lag by seconds” beside search, “public event details only” beside the cache, and “seat truth” beside inventory. A cache placed generically between every service and every database obscures more than it explains. What is cached, under which key, with what freshness and permission rule, is the architecture.

Derived data also needs a way home. Search can be rebuilt from canonical event and listing records. Cache entries can expire or be invalidated. If a seller unpublishes an event, the public read path must fail closed or check canonical visibility until the derived copies catch up. Calling a store “eventually consistent” does not settle what the user is allowed to see during the wait.

Branch only after confirmation

Once the order transaction commits, several consumers need to learn about it:

order transaction
  -> outbox: OrderConfirmed
  -> relay -> durable queue or log
       -> email worker -> email provider
       -> seller-dashboard projection
       -> analytics pipeline
       -> recommendation and search updaters

Write the event to an outbox with the order so that a process crash cannot leave a confirmed purchase with no event. A relay can publish it repeatedly, so consumers must recognize the event or apply its effect idempotently. State whether ordering matters, where repeated failures go, and how operators replay or repair missed work.

The queue is not a reliability halo. It changes immediate failure into backlog, duplication, reordering, and poison-message problems. Put the promises on the arrows: OrderConfirmed, at-least-once delivery, event ID as the deduplication identity, and queue age as the freshness signal. If email has failed for twenty minutes, the purchase can remain true while the interface offers a resend. If ticket delivery itself is deferred, the order page must show that state rather than pretend the side effect finished.

The external email provider belongs on the worker path. The payment provider does not. “Third party” describes ownership, not whether a call should be asynchronous; the product promise decides where the dependency sits.

Let boundaries earn their boxes

The diagram now contains a few meaningful boundaries:

  • Edge/gateway isolates public traffic, authentication handoff, request limits, and routing. It does not make product decisions.
  • Purchase owns holds, order confirmation, the seat invariant, and the idempotency record. It is the authority for whether this buyer owns this ticket.
  • Payment adapter contains provider-specific calls, timeouts, webhooks, duplicate handling, and reconciliation without granting the provider ownership of local orders.
  • Discovery serves stale-tolerant public reads from search and caches. It cannot reserve inventory.
  • Background consumers own particular projections or side effects and can fail without rolling back a confirmed sale.

These may be modules, processes, or services. A high-level interview diagram does not need to decide every deployment boundary immediately. If two boxes have the same owner, scale profile, transaction needs, and failure behavior, merging them may make the design more honest.

Use a separate box when the separation changes reasoning: a trust boundary, a distinct scaling shape, a required failure barrier, independent ownership, or an external system. “User service, ticket service, order service, notification service” names nouns. “Purchase owns the sale; discovery serves repairable copies” names consequences.

Stress the diagram without redrawing it

The first coherent diagram is a hypothesis. Annotate the boundaries where it could become false or slow:

  • Beside inventory, write hold-conflict rate, hold expiry, and the latency of the confirmation transaction. A popular event may serialize work around a small set of seats.
  • Beside payment, write timeout, authorization error rate, provider latency, duplicate webhook, and reconciliation backlog. Decide when the user sees pending rather than a false failure or false success.
  • Beside the outbox and queue, write oldest unpublished event, queue age, retry count, dead-letter volume, and consumer lag.
  • Beside discovery, write indexing lag and cache hit rate, then state how a stale result is corrected at reserve time.
  • At the edge, mark the public trust boundary, abuse limits, and whether traffic can be shed before it reaches scarce inventory.

Observability is useful here because each signal tests a claim in the drawing. Queue age tests the promise that deferred work catches up. Reconciliation backlog tests the gap between external payment and local truth. Hold conflicts reveal the next scaling problem. “Add logs, metrics, and traces” is less useful than naming the uncertainty an instrument resolves.

Follow one failure all the way through. Suppose the payment provider times out after accepting an authorization. The request must not be retried under a new identity. The attempt remains pending, a webhook or reconciliation query resolves it, the local transition is idempotent, and the user can inspect the current order state. That trace validates the ownership boundaries better than another row of components.

Choose the next deep dive from exposed risk

The diagram should make the next ten minutes obvious. For the ticket marketplace, hot-event inventory is the first deep dive because correctness and concentrated load meet there. Payment reconciliation is the second because an external system and local state can disagree. Search freshness is important, but checkout revalidation limits its harm.

Say the choice and the rejected alternative:

I would go deeper on inventory under a hot event. It owns the no-oversell
invariant, and one event can concentrate both reads and writes. Search also
gets hot, but it is derived and checkout revalidates availability, so stale
discovery is less dangerous than conflicting sales.

If the interviewer changes a requirement, alter the path before multiplying boxes. Assigned seating exposes contention around individual seats. General admission turns the invariant into a bounded counter. Delayed capture changes payment states and reconciliation. Seller data residency changes placement. Each variation should travel through ownership, state, arrows, and the user-visible response.

Narrate what the drawing asserts

A compact explanation might sound like this:

The gateway authenticates and rate-limits public traffic. Discovery uses a
derived search index and cache, so it may lag; reserve and checkout return to
canonical inventory. The purchase boundary owns holds, confirmed orders, and
the no-oversell invariant. Checkout is idempotent and calls the payment
provider before confirmation, with a pending state and reconciliation for
ambiguous outcomes. The order transaction writes an outbox event. Email,
seller dashboards, analytics, recommendations, and search update from that
event asynchronously. I would next examine hot-event inventory because it is
where correctness and concentrated load collide.

An interviewer can interrupt any sentence and ask what happens next. The answer is already latent in the diagram: data, owner, guarantee, failure, and repair.

If your drawing has become crowded, recover in public:

Let me return to the causal path: request, decision owner, canonical state,
response, then deferred effects. I will add back only the components that
protect a specific constraint or isolate a specific risk.

Practice on resistant flows

Use one known prompt rather than inventing a fresh architecture for every drill. First draw only the user-visible write and its canonical store. Narrate the arrows in six sentences. Then add one derived read and state its freshness, authorization, and rebuild behavior. Finally add one asynchronous side effect and annotate delivery, duplicate handling, repeated failure, queue age, and what the user sees while it is pending.

Now vary the prompt in a way that resists copying. For collaborative editing, the hot object is a document rather than a seat. For photo upload, bytes may bypass the application and processing is part of availability. For alerting, notification delivery may be the product result rather than a best-effort side effect. Redraw the arrows whose promises changed; do not merely rename boxes.

Before moving on, check the drawing in plain language:

  • Can you point to the operation that determines user-visible success?
  • Is every consequential fact owned once?
  • Can you distinguish canonical state from caches, search, analytics, and other derived views?
  • Does every async branch name its event, lag signal, duplicate behavior, and repair path?
  • Can you trace one ambiguous dependency failure through to a user-visible state?
  • Does the next deep dive follow from risk rather than familiarity?

Field reference

ARCHITECTURE FLOW

1. State success
   actor, operation, required truth, allowed lag

2. Draw the synchronous path
   client -> ingress -> decision owner -> canonical state -> response

3. Draw distinct reads
   authoritative or derived; freshness, permission, rebuild

4. Add deferred work
   event -> durable handoff -> consumer -> side effect or projection
   delivery, idempotency, ordering, failure, lag, repair

5. Label boundaries
   decision, invariant, trust, scale, failure, external ownership

6. Stress the arrows
   bottleneck, ambiguous failure, staleness, abuse, proof signal

7. Choose the deep dive
   highest product risk, plus one rejected alternative

Senior rule:
If you cannot narrate an arrow, the diagram is not finished.

The high-level architecture is complete when it explains what the system may claim, who may make the claim true, what can wait, and how disagreement is found and repaired. In this design, that leaves one boundary deliberately under pressure: scarce inventory during a hot event. Scaling and Partitioning can now decide where that load and ownership should live. The preceding API and Data Modeling supplies the promises and invariants that gave these boxes their work.