Back to Scenarios & Design Questions
Q19Question 19 of 67

How do you approach a broad "Design X" question in an interview?

Key Concepts
  • Start with requirements and scoping: clarify features, users, scale, and separate functional from non-functional requirements.
  • Do a quick back-of-envelope estimate (DAU x actions = QPS, content size x catalog = storage) to justify later technology choices.
  • Decompose by user journeys, not abstract boxes: walk through what a real user does to reveal independent components.
  • For each component follow Requirement then Responsibility then Data then Technology, in that strict order.
  • Introduce Redis, Kafka, CDN, or sharding only at the point where a named bottleneck justifies it, never technology-first.
  • Go breadth-first first so the interviewer sees you can scope the whole system, then deepen where they steer.
  • Distinguish the read path from the write path because they usually have very different scale and consistency needs.

Definition

Decompose a "Design X" question by user journeys into components, and for each one reason Requirement then Responsibility then Data then Technology, introducing Redis, Kafka, a CDN, or sharding only where a named bottleneck proves it is needed, never technology-first.
  • Requirements Phase – clarify functional requirements (features), non-functional requirements (scale, latency, availability, consistency), and do a back-of-envelope estimate so numbers later justify each technology.
  • Decomposition by User Journey – walk through what a real user does (sign up, browse, transact) and let each journey reveal independent components, not abstract boxes.
  • RRDT Chain – for each component: Requirement (what must it achieve), Responsibility (single job), Data (what, how much, read/write ratio), then Technology (justified by the data).
  • Bottleneck-Driven Technology – start each component with the simplest thing that works (app server + relational DB) and add Redis, Kafka, CDN, or sharding only where a computed bottleneck proves it is needed.
  • Breadth-First, Then Depth – sketch the whole picture at a shallow level first, then dive into the one or two components the interviewer steers you toward.

Imagine National Bank of India asks you to "design a new branch." The weak approach is to immediately shout "put in a huge vault, a fleet of armored trucks, and ten ATMs" before anyone has said how many customers come or what they do. The strong approach is to first ask who uses the branch and what they come to do, then for each customer journey decide what that counter needs, what records it handles, and only then choose whether it needs a small cash drawer or a full vault.

Understanding the Concepts

How do you start: requirements and scoping?

The first phase is requirements and scoping, and skipping it is the most common interview failure. You ask clarifying questions to bound the problem: which features are in scope, who the users are, and what the scale is. Then separate functional from non-functional requirements and do a quick estimate.

JAVA
package com.bank.design;

public class RequirementsPhase {

    public static void main(String[] args) {
        // Functional requirements for "Design Mobile Banking"
        System.out.println("Functional: login, view balance, transfer, statements");

        // Non-functional requirements
        long dailyActiveUsers = 10_000_000L;
        int actionsPerUser = 5;
        long qps = dailyActiveUsers * actionsPerUser / 86400; // ~578 QPS
        System.out.println("Estimated QPS: " + qps);
        System.out.println("Peak QPS (10x): " + (qps * 10));
        System.out.println("Availability target: 99.99%");
        System.out.println("Latency target: p99 < 200ms");
    }
}
TEXT
Functional: login, view balance, transfer, statements
Estimated QPS: 578
Peak QPS (10x): 5780
Availability target: 99.99%
Latency target: p99 < 200ms

How do you decompose by user journeys?

Rather than inventing an abstract box diagram, walk through what a real user does and let that reveal the independent components.

JAVA
package com.bank.design;

import java.util.List;

public class UserJourneyDecomposition {

    record Journey(String name, List<String> components) {}

    public static void main(String[] args) {
        List<Journey> journeys = List.of(
            new Journey("Login", List.of("AuthService", "SessionStore")),
            new Journey("View Balance", List.of("AccountService", "BalanceCache")),
            new Journey("Transfer Money", List.of("TransferService", "LedgerService", "NotificationService")),
            new Journey("Download Statement", List.of("StatementService", "DocumentStore"))
        );

        journeys.forEach(j -> System.out.println(
            "Journey: " + j.name() + " → Components: " + j.components()));
    }
}
DIAGRAM
Journey: Login → Components: [AuthService, SessionStore]
Journey: View Balance → Components: [AccountService, BalanceCache]
Journey: Transfer Money → Components: [TransferService, LedgerService, NotificationService]
Journey: Download Statement → Components: [StatementService, DocumentStore]

What is the Requirement-Responsibility-Data-Technology chain?

For each component you follow a strict chain. Restate its requirement, name its single responsibility, reason about its data, and only then choose the technology justified by that data.

JAVA
package com.bank.design;

public class RRDTChain {

    public static void main(String[] args) {
        System.out.println("=== Account Service ===");
        System.out.println("Requirement: let users check balance with <50ms latency");
        System.out.println("Responsibility: owns account balances, nothing else");
        System.out.println("Data: 10M accounts, 200 bytes each = 2GB, 90% reads");
        System.out.println("Technology: PostgreSQL (source of truth) + Redis cache");
        System.out.println("  Redis justified because: 90% read ratio, 5000+ reads/sec,");
        System.out.println("  DB alone would struggle at peak, cache hit rate >95%");

        System.out.println();
        System.out.println("=== Transfer Service ===");
        System.out.println("Requirement: process money transfers with exactly-once semantics");
        System.out.println("Responsibility: owns transfer lifecycle, nothing else");
        System.out.println("Data: ~100 transfers/sec, 1KB each, write-heavy");
        System.out.println("Technology: PostgreSQL + Kafka for event fan-out");
        System.out.println("  Kafka justified because: must fan out to notifications, audit,");
        System.out.println("  and fraud without blocking the transfer response");
    }
}
TEXT
=== Account Service ===
Requirement: let users check balance with <50ms latency
Responsibility: owns account balances, nothing else
Data: 10M accounts, 200 bytes each = 2GB, 90% reads
Technology: PostgreSQL (source of truth) + Redis cache
  Redis justified because: 90% read ratio, 5000+ reads/sec,
  DB alone would struggle at peak, cache hit rate >95%

=== Transfer Service ===
Requirement: process money transfers with exactly-once semantics
Responsibility: owns transfer lifecycle, nothing else
Data: ~100 transfers/sec, 1KB each, write-heavy
Technology: PostgreSQL + Kafka for event fan-out
  Kafka justified because: must fan out to notifications, audit,
  and fraud without blocking the transfer response

Why must technology be justified by a bottleneck, not fashion?

You start each component with the simplest thing that could work and then ask "what breaks as we scale?" Each technology is introduced at the point where a real bottleneck justifies it.

JAVA
package com.bank.design;

public class BottleneckDrivenDesign {

    public static void main(String[] args) {
        // WRONG: technology-first
        System.out.println("WRONG: 'Let's add Redis, Kafka, and a CDN because Netflix has them'");

        // CORRECT: bottleneck-first
        System.out.println("CORRECT: 'Balance reads are 5000/sec, DB can handle 1000/sec,");
        System.out.println("  so we add Redis to absorb the 4000/sec read surplus'");
        System.out.println();
        System.out.println("CORRECT: 'Statements are 50MB PDFs served to global users,");
        System.out.println("  origin bandwidth is the bottleneck, so a CDN is justified'");
        System.out.println();
        System.out.println("NO CACHE for TransferService: it is write-heavy,");
        System.out.println("  caching does not help writes, do not add one just because others have it");
    }
}
TEXT
WRONG: 'Let's add Redis, Kafka, and a CDN because Netflix has them'
CORRECT: 'Balance reads are 5000/sec, DB can handle 1000/sec,
  so we add Redis to absorb the 4000/sec read surplus'

CORRECT: 'Statements are 50MB PDFs served to global users,
  origin bandwidth is the bottleneck, so a CDN is justified'

NO CACHE for TransferService: it is write-heavy,
  caching does not help writes, do not add one just because others have it

How do you distinguish the read path from the write path?

The read path and the write path usually have very different scale and consistency needs. Conflating them produces a muddled design.

JAVA
package com.bank.design;

public class ReadWritePaths {

    public static void main(String[] args) {
        System.out.println("=== Balance Service ===");
        System.out.println("Read path: 5000 req/sec, can tolerate 2s staleness, use Redis cache");
        System.out.println("Write path: 100 req/sec, must be strongly consistent, write to DB");
        System.out.println();
        System.out.println("=== Statement Service ===");
        System.out.println("Write path: batch, 50 statements/min, generate and store in S3");
        System.out.println("Read path: on-demand, serve large PDFs to global users via CDN");
    }
}
TEXT
=== Balance Service ===
Read path: 5000 req/sec, can tolerate 2s staleness, use Redis cache
Write path: 100 req/sec, must be strongly consistent, write to DB

=== Statement Service ===
Write path: batch, 50 statements/min, generate and store in S3
Read path: on-demand, serve large PDFs to global users via CDN

How do you address non-functional requirements across the whole design?

After detailing each component, walk the critical path of the main user journey and cover availability, consistency, and single points of failure.

JAVA
package com.bank.design;

public class NonFunctionalCoverage {

    public static void main(String[] args) {
        System.out.println("Availability: multi-region replication, failover in <30s");
        System.out.println("Consistency: strong for transfers (money), eventual for balances (display)");
        System.out.println("Single points of failure: eliminated by redundancy at every layer");
        System.out.println("Critical path latency:");
        System.out.println("  Client → CDN (5ms) → Gateway (10ms) → Service (20ms)");
        System.out.println("  → Cache hit (1ms) = total ~36ms < 200ms target");
    }
}
DIAGRAM
Availability: multi-region replication, failover in <30s
Consistency: strong for transfers (money), eventual for balances (display)
Single points of failure: eliminated by redundancy at every layer
Critical path latency:
  Client → CDN (5ms) → Gateway (10ms) → Service (20ms)
  → Cache hit (1ms) = total ~36ms < 200ms target

What are common mistakes in system design interviews?

The most common failure is jumping to technologies before establishing the problem. The second is not doing back-of-envelope math to justify each choice.

JAVA
package com.bank.design;

// WRONG: technology-first, no justification
public class WrongApproach {
    // "We need Redis, Kafka, Elasticsearch, MongoDB, and a CDN"
    // No requirements, no numbers, no justification
    // Interviewer asks: "Why Redis?" Answer: "Everyone uses it" ← FAIL
}

// CORRECT: requirement-driven, every choice justified
public class CorrectApproach {
    // "Balance reads are 5000/sec, 90% read ratio, DB tops out at 1000/sec"
    // "So Redis absorbs the surplus, hit rate >95% = justified"
    // Interviewer asks: "Why Redis?" Answer: numbers and bottleneck ← PASS
}

What are common interview follow-up questions?

  • How would you handle a 10x traffic spike on this design?
  • Which component would you shard first and why?
  • What happens if the message queue goes down during a transfer?
  • How do you ensure exactly-once delivery for financial transactions?
  • What would you change if the latency target dropped to 50ms?

Visual Overview

DIAGRAM
  System Design Interview Framework
  ──────────────────────────────────

  Phase 1: Requirements & Scoping
  ┌─────────────────────────────────────────┐
  │ Functional: login, balance, transfer    │
  │ Non-functional: 10M DAU, 99.99%, <200ms │
  │ Estimate: 578 QPS avg, 5780 QPS peak    │
  └────────────────┬────────────────────────┘
                   │
  Phase 2: Decompose by User Journeys
  ┌────────────────┴────────────────────────┐
  │ Login → Auth + Session                  │
  │ Balance → Account + Cache               │
  │ Transfer → Transfer + Ledger + Notify   │
  │ Statement → Statement + DocStore        │
  └────────────────┬────────────────────────┘
                   │
  Phase 3: RRDT Chain per Component
  ┌────────────────┴────────────────────────┐
  │ Requirement → Responsibility → Data     │
  │                                → Tech   │
  │ (technology LAST, justified by numbers)  │
  └────────────────┬────────────────────────┘
                   │
  Phase 4: Non-Functional Across Design
  ┌────────────────┴────────────────────────┐
  │ Availability, Consistency, SPOF,        │
  │ Critical path latency, Trade-offs       │
  └─────────────────────────────────────────┘

Key Takeaway

When facing a broad "Design X" question, drive the design top-down from what the system must do, not bottom-up from technologies you want to name. Start with requirements and scoping: clarify features, separate functional from non-functional, and do back-of-envelope math because those numbers justify every later technology choice.

Decompose by user journey rather than drawing abstract boxes. Walk through what a real user does and let each journey reveal an independent component with one clear responsibility. For each component follow the strict RRDT chain: Requirement, Responsibility, Data, then Technology, with every technology defended by a number or a bottleneck, never by fashion.

The discipline that most impresses interviewers is starting each component with the simplest thing that works and introducing infrastructure only at a proven bottleneck. Add Redis when reads dominate and the database is the constraint. Add Kafka when events must fan out without blocking the user. Add a CDN when large content goes to a global audience. Add sharding when data exceeds a single node. Every choice is a conclusion, not a starting assumption.

Remember one sentence: a good system design flows from requirements to responsibilities to data to technology, where every decision is justified by a number, not by fashion.

---

Share
Use ← → arrow keys to navigate