Problem RestatementProblem
Many interviews (JPMorgan, Visa and others) include a round where you explain a real project you built: what it does, how data flows from the trigger to the output, what each component owns, how failures are handled, and what you personally did. A common follow-up (asked at Visa for a Staff role): "Now imagine it has to work at the scale of Facebook or Google. Redesign it."
This page gives a structure for that answer, and a worked example of scaling a typical project.
Deep Dive — How to structure the explanationDeep dive
You have ten to fifteen minutes to explain something you spent two years on. The structure you pick decides whether the interviewer learns what you can do.
Tell it chronologically
"We started with a monolith, then in Q2 we added a queue, then we migrated to Postgres..."
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
T["Timeline narration"] --> H["History the interviewer cannot evaluate"]
H --> NOW["Ten minutes gone, the current system never described"]
T --> ME["No separation between what the team did and what you did"]
ME --> UNK["Your actual contribution stays unknown"]The order the system was built in is not the order it is understood in. Worse, a chronology hides ownership: nothing in it distinguishes a decision you made from one you inherited, which is the thing being assessed.
Tour the components
Walk through the boxes: API layer, services, databases, queues, external systems.
Much better — the interviewer now has a picture, and this is the right backbone. What a tour leaves out is motion: a list of components does not show how a request becomes a result, which means the interesting parts — where state changes, where things can fail, where you had to choose — never come up.
Follow one request, then go deep on what was hard
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
C["Context - 1 min: problem, users, rough scale"] --> A["Architecture - 3-4 min: trace ONE request end to end"]
A --> D["Data - 2 min: what lives where, who owns it, consistency needs"]
D --> H["Hard parts - 3 min: one or two problems you actually solved"]
H --> WHAT["What you tried, what worked, what it cost"]
WHAT --> Y["Your role - explicit: what you designed and decided"]
Y --> Q["Leave time for follow-ups - the scaling question is coming"]- Trace a single request end to end. It forces every component to justify its existence and naturally surfaces the queues, caches and failure points in the order they matter.
- Spend the middle on one or two hard problems — a race condition, a slow query, an outage — and say what you tried, what worked, and what it cost. This is the only part that distinguishes you from someone describing a system they read about.
- State your own role explicitly. "I designed X, I decided Y" is not bragging; it is answering the question, and interviewers cannot assume it.
- Give the numbers early. Requests per second and data size let the interviewer calibrate everything that follows — without them, they cannot tell whether your choices were reasonable.
Stop before the time is up. The follow-up — usually "now make it a hundred times bigger" — is where the strongest signal is, and running to the buzzer costs you that.
Example Project (Before Scaling)
An internal order notification service: when an order ships, it sends an email and SMS to the customer.
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
OS["Order Service"] -->|"REST call"| NS["Notification Service"]
NS --> DB[("Postgres - templates, logs")]
NS --> EM["Email provider"]
NS --> SMS["SMS provider"]- Scale today: 50K notifications/day, one region, 2 app instances, one Postgres.
- A hard part solved: the SMS provider sometimes timed out, so orders waited and sometimes got 2 texts. Fix: added timeouts, an idempotency key per (order, channel), and moved sending to a background worker.
Redesign for Planet ScaleScale
Assume 100x–1000x traffic: 50M notifications/day across many regions, with spikes (sales events). Walk through what breaks first, then fix it:
| What breaks | Why | Fix |
|---|---|---|
| Synchronous REST call from Order Service | Slow providers block orders, spikes overload us | Publish an "order shipped" event to Kafka. Notification consumers process it asynchronously |
| Single Postgres | Write volume and single point of failure | Shard the notification log by user ID. Keep templates in a small replicated DB plus cache. Primary + replicas per shard |
| One region | Latency for global users, region outage = no notifications | Multi-region deployment. Events processed in the user's home region. Replicate critical data |
| Providers' rate limits | Spikes exceed provider quotas | Per-provider rate limiters, priority queues (transactional before marketing), multiple providers with failover |
| Duplicate sends on retries | At-least-once processing | Idempotency key (event_id, channel) stored with a TTL. Check before sending |
| No visibility | Hard to debug at scale | Metrics per channel and provider, tracing by event ID, dead-letter queue with alerts |
3.1 Redesigned Architecture
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
OS["Order Service"] -->|"OrderShipped event"| K[("Kafka - partitioned by user")]
K --> W["Notification workers - per region"]
W --> ID[("Idempotency store")]
W --> PREF[("User prefs + templates cache")]
W --> RL["Per-provider rate limiter"]
RL --> P1["Email providers"]
RL --> P2["SMS providers"]
W -->|"failed after retries"| DLQ[("Dead-letter queue")]
W --> LOG[("Sharded delivery log")]3.2 Talking points that show depth
- Consistency: notifications can be eventually consistent. The order DB stays strongly consistent. Say this out loud.
- Replication (the key discussion at Visa): explain leader-follower replication, synchronous vs asynchronous replication (data loss vs latency), and how failover works.
- Cost: SMS is expensive, so batch and deduplicate, respect user preferences, and prefer push notifications when possible.
- Rollout: move from sync to async gradually. Dual-run for a week and compare delivery counts before switching off the old path.
Common Follow-up QuestionsFollow-ups
- "What would you do differently?" Pick something real, like "I'd have made it event-driven from day one" or "I'd add load tests before launch".
- "How did you know it worked?" Metrics, dashboards, alerts and the before/after numbers.
- "What if the database is down?" Explain failover, what users see during it, and how data is protected (backups, point-in-time recovery).
Wrap-UpWrap-up
Present your project in a fixed order: context and scale, the boxes and one request's path, data ownership, one or two hard problems with numbers, operations, and your role. To scale it to planet size, name what breaks first and fix it step by step: make it async with events, shard and replicate data, go multi-region, respect provider limits, add idempotency, and build observability, while being honest about the trade-offs.