Skip to content
DSA Grind
All 26 sections

LLD — Top Questions & Framework (MAANG SDE-II / L4 / E4)

NoteUpdated
On this page

Target: Sanjay Naik — 3.5 yrs Java/Spring Boot, Jio Platform Ltd, healthcare microservices. Scope: Low-Level Design rounds at Amazon, Atlassian, Flipkart, Uber, Walmart Labs, Meta, Google.

1. What LLD interviews test in 2026

LLD = “Can you turn a vague problem into clean, extensible Java code in 45 minutes?”

What’s actually graded:

  • OOP fluency — encapsulation, inheritance vs composition, polymorphism (favor composition).
  • SOLID — interviewer explicitly looks for SRP & OCP violations.
  • Design patterns — apply 1–3 patterns, name them out loud, justify the choice.
  • UML-lite class diagram — class names, relationships (has-a, is-a), multiplicities.
  • Clean Java — interfaces over concretions, enums for fixed sets, immutability for value objects, thread-safety where state is shared.
  • Extensibility — “what if we add X?” must require minimal code change (OCP).
  • Trade-offs — articulate why you picked Strategy over if-else, Singleton vs DI, etc.

Round structure by company (2026):

Company LLD round Notes
Amazon Mandatory at SDE-II 60 min, often combined with bar-raiser LP questions
Atlassian Mandatory Dedicated 60-min “craft” round, code on shared editor
Flipkart Mandatory at SDE-II Machine coding 90 min — must run
Uber Mandatory LLD + concurrency emphasis
Walmart Labs Mandatory LLD + DB schema combined
Meta HLD-heavy LLD shows up inside coding round as follow-up
Google HLD-heavy LLD via “design this class” inside coding
Microsoft Mandatory at SDE-II Machine coding round

If targeting Flipkart/Atlassian: practice typing the full thing in IntelliJ in 90 min, not just whiteboarding.


2. Top 20 LLD problems asked at MAANG / Tier-1

Difficulty: E (easy, ~30 min) / M (medium, ~45 min) / H (hard, 60–90 min).

# Problem Difficulty Dominant Pattern(s) Frequently Asked At
1 Parking Lot M Strategy (fee), Factory (spot), Singleton Amazon, Flipkart, Uber, Walmart
2 Elevator System H State, Strategy (scheduling), Observer Amazon, Uber, Atlassian
3 Splitwise M Strategy (split type), Observer Amazon, Atlassian, Flipkart
4 LRU Cache E Doubly-LinkedList + HashMap, Template Meta, Google, Amazon
5 Logger / Logging Framework M Chain of Responsibility, Singleton, Strategy (formatter) Amazon, Atlassian
6 Rate Limiter (Sanjay’s project) M Strategy (algo), Decorator, Singleton Amazon, Uber, Stripe, Atlassian
7 Vending Machine E State, Strategy Amazon, Walmart
8 Snake & Ladder / Tic-Tac-Toe E Strategy (win check), Factory (cell) Microsoft, Flipkart
9 Chess H Strategy (move per piece), Factory, Command (undo) Amazon, Google
10 BookMyShow / Movie Ticket Booking M Strategy (seat selection), Observer, Pessimistic Lock Flipkart, BMS, Atlassian
11 Library Management E Factory, Strategy (search) Amazon, Walmart
12 ATM M State, Chain of Responsibility (notes dispenser) Amazon, Walmart
13 Hotel Booking M Strategy (room pricing), Factory Booking, MMT, Amazon
14 Cab Booking (Uber-LLD) H Strategy (pricing/matching), Observer, State Uber, Ola, Amazon
15 Notification System M Strategy (channel), Observer, Decorator Amazon, Atlassian, Meta
16 File System / Linux ls M Composite, Visitor (search) Amazon, Google, Microsoft
17 In-memory Key-Value Store M Strategy (eviction), Template, Observer Amazon, Meta
18 Stack Overflow / Reddit H Strategy (sort), Observer, Composite (comments) Amazon, Atlassian
19 Pub-Sub System H Observer, Strategy (delivery), Decorator Amazon, Uber, Meta
20 Card Game / Deck of Cards E Factory, Strategy, Iterator Amazon, Microsoft

Recommended order for Sanjay:

  1. Parking Lot (the canonical warm-up — solve in 30 min cold).
  2. Rate Limiter (your project — must be airtight).
  3. Notification System (omnichannel — maps to your work).
  4. LRU Cache (you’ve built caching at Jio).
  5. Splitwise + BookMyShow (concurrency + locking).
  6. Then breadth.

3. SOLID refresher (Java framing)

Principle One-liner Wrong Right
S — Single Responsibility One class, one reason to change User class does DB + email + validation Split into UserRepository, UserNotifier, UserValidator
O — Open/Closed Open to extend, closed to modify if (type=="GST") ... else if (type=="VAT") TaxStrategy interface + impls, new tax = new class
L — Liskov Substitution Subtypes must be substitutable Square extends Rectangle overrides setWidth to also set height — breaks callers Keep them sibling impls of Shape
I — Interface Segregation Many small interfaces > one fat one Worker with work(), eat(), sleep() forces robots to implement eat() Split into Workable, Eatable
D — Dependency Inversion Depend on abstractions class OrderService { MySQLOrderRepo repo = new MySQLOrderRepo(); } Inject OrderRepository interface via constructor

Mnemonic: “Software Objects Live In Dependencies.”


4. The 12 design patterns you MUST know at L4

For each: purpose → when → JDK example → LLD problem where it shines.

Creational

1. Singleton

  • Purpose: exactly one instance, global access.
  • When: shared registry, config, connection pool. Beware: makes testing harder.
  • JDK: Runtime.getRuntime().
  • LLD: Parking Lot entry point, Logger.
  • Java idiom (thread-safe, lazy): static holder or enum.

2. Factory Method

  • Purpose: defer instantiation to subclass / method.
  • When: client shouldn’t know concrete type.
  • JDK: Calendar.getInstance().
  • LLD: ParkingSpot creation, Vehicle creation.

3. Abstract Factory

  • Purpose: factory of factories — families of related objects.
  • When: cross-platform UI, FHIR vs OpenEHR mapper families (Sanjay’s actual code).
  • JDK: DocumentBuilderFactory.
  • LLD: Notification channel factories (SMS/Email/Push each with own builder).

4. Builder

  • Purpose: stepwise construction of complex immutable objects.
  • When: >4 constructor args, optional fields.
  • JDK: StringBuilder, Stream.Builder.
  • LLD: HTTP request, Pizza/Burger builder, Ticket builder.

5. Prototype

  • Purpose: clone existing object instead of new construction.
  • When: expensive creation, registry of templates.
  • JDK: Object.clone(), Cloneable.
  • LLD: Document templates, game piece spawning.

Structural

6. Adapter

  • Purpose: convert one interface to another.
  • When: integrating 3rd-party / legacy.
  • JDK: Arrays.asList(), InputStreamReader.
  • LLD: Payment gateways (Stripe vs Razorpay adapters).

7. Decorator

  • Purpose: add behavior dynamically without subclassing.
  • When: layered behavior (logging + caching + retry).
  • JDK: BufferedReader(new FileReader(...)).
  • LLD: Notification (add encryption / retry layers), Pizza toppings.

Behavioral

8. Observer

  • Purpose: publish/subscribe — notify many on state change.
  • When: events, listeners.
  • JDK: java.util.Observer (deprecated), PropertyChangeListener, Spring ApplicationEventPublisher.
  • LLD: Pub-Sub, Notification system, Stock price tracker.

9. Strategy

  • Purpose: swap algorithm at runtime via interface.
  • When: many ways to do one thing (sort, price, throttle).
  • JDK: Comparator.
  • LLD: Rate Limiter algo, parking fee, seat selection, eviction policy.

10. Command

  • Purpose: encapsulate request as object — supports undo, queue, log.
  • When: undo/redo, job queues, macro recording.
  • JDK: Runnable, Callable.
  • LLD: Chess (undo), text editor, remote control.

11. Template Method

  • Purpose: skeleton in base class, steps overridden by subclass.
  • When: fixed flow with variable steps.
  • JDK: AbstractList, HttpServlet.service().
  • LLD: Logger pipeline, ETL job, payment flow.

12. State

  • Purpose: behavior changes with internal state — replace if-else on status field.
  • When: object goes through a finite state machine.
  • JDK: Thread.State (enum, conceptual).
  • LLD: Vending Machine, ATM, Elevator, Order lifecycle.

5. LLD answer framework — 45-min budget

Treat as a metronome. Talk while you code.

Phase Time What to do What to say out loud
1. Clarify requirements 5 min Functional + non-functional. Scope cuts. “Single building or multi? Payment in scope? Concurrent users?”
2. Actors & use-cases 5 min Who triggers what. Bullet list. “Driver enters, system assigns spot, prints ticket, driver exits, pays.”
3. Class diagram 10 min Entities + relationships. Whiteboard or comment block. ParkingLot HAS-A List<ParkingFloor>; ParkingFloor HAS-A List<ParkingSpot>.”
4. Method signatures + relationships 10 min Interfaces first, then concretes. No bodies yet. Ticket assignSpot(Vehicle v) returns the ticket, throws NoSpotAvailable.”
5. Code 1–2 key methods 10 min The interesting one — usually the algorithm or the locking. “Let me implement parkVehicle with the spot strategy and synchronized block.”
6. Trade-offs + extensions 5 min “If we add X, only Y changes.” “Adding EV spot = new enum + new strategy impl. No existing class changes.”

Red flags that eat your clock: bikeshedding on enum names, drawing UML too pretty, implementing getters/setters.


6. Parking Lot — worked example

Patterns used: Singleton (entry), Factory (spot creation), Strategy (fee), Enum (types).

// ----- Enums -----
public enum VehicleType { MOTORCYCLE, CAR, TRUCK }
public enum SpotType    { COMPACT, REGULAR, LARGE }
public enum TicketStatus { ACTIVE, PAID, LOST }

// ----- Vehicle hierarchy -----
public abstract class Vehicle {
    private final String licensePlate;
    private final VehicleType type;
    protected Vehicle(String plate, VehicleType type) {
        this.licensePlate = plate;
        this.type = type;
    }
    public VehicleType getType() { return type; }
    public String getLicensePlate() { return licensePlate; }
}
public class Car        extends Vehicle { public Car(String p)        { super(p, VehicleType.CAR); } }
public class Motorcycle extends Vehicle { public Motorcycle(String p) { super(p, VehicleType.MOTORCYCLE); } }
public class Truck      extends Vehicle { public Truck(String p)      { super(p, VehicleType.TRUCK); } }

// ----- Spot -----
public class ParkingSpot {
    private final String id;
    private final SpotType type;
    private Vehicle occupant;     // null when free

    public ParkingSpot(String id, SpotType type) { this.id = id; this.type = type; }
    public boolean isFree()           { return occupant == null; }
    public boolean canFit(Vehicle v)  { /* truck→LARGE; car→REGULAR/LARGE; moto→any */ return true; }
    public synchronized boolean assign(Vehicle v) {
        if (occupant != null) return false;
        occupant = v; return true;
    }
    public synchronized void release() { occupant = null; }
    public SpotType getType()         { return type; }
    public String getId()             { return id; }
}

// ----- Floor -----
public class ParkingFloor {
    private final int floorNumber;
    private final List<ParkingSpot> spots;
    public ParkingFloor(int n, List<ParkingSpot> spots) { this.floorNumber = n; this.spots = spots; }
    public Optional<ParkingSpot> findSpot(Vehicle v) {
        return spots.stream().filter(s -> s.isFree() && s.canFit(v)).findFirst();
    }
}

// ----- Ticket -----
public class Ticket {
    private final String id;
    private final String licensePlate;
    private final String spotId;
    private final Instant entryTime;
    private Instant exitTime;
    private TicketStatus status = TicketStatus.ACTIVE;
    private BigDecimal amount;
    // ctor + getters/setters
}

// ----- Strategy: fee -----
public interface FeeStrategy {
    BigDecimal calculate(Ticket t, VehicleType type);
}
public class HourlyFeeStrategy implements FeeStrategy {
    public BigDecimal calculate(Ticket t, VehicleType type) { /* hours * rate(type) */ return null; }
}
public class FlatFeeStrategy implements FeeStrategy {
    public BigDecimal calculate(Ticket t, VehicleType type) { return new BigDecimal("100"); }
}

// ----- Factory: spot creation -----
public class SpotFactory {
    public static ParkingSpot create(String id, SpotType type) {
        return new ParkingSpot(id, type); // can branch on type for sub-classes
    }
}

// ----- Singleton: ParkingLot -----
public final class ParkingLot {
    private static volatile ParkingLot INSTANCE;
    private final List<ParkingFloor> floors;
    private final Map<String, Ticket> activeTickets = new ConcurrentHashMap<>();
    private FeeStrategy feeStrategy;

    private ParkingLot(List<ParkingFloor> floors, FeeStrategy fee) {
        this.floors = floors; this.feeStrategy = fee;
    }
    public static ParkingLot getInstance(List<ParkingFloor> floors, FeeStrategy fee) {
        if (INSTANCE == null) {
            synchronized (ParkingLot.class) {
                if (INSTANCE == null) INSTANCE = new ParkingLot(floors, fee);
            }
        }
        return INSTANCE;
    }

    public Ticket parkVehicle(Vehicle v) {
        for (ParkingFloor f : floors) {
            Optional<ParkingSpot> spot = f.findSpot(v);
            if (spot.isPresent() && spot.get().assign(v)) {
                Ticket t = new Ticket(/* uuid */, v.getLicensePlate(), spot.get().getId(), Instant.now());
                activeTickets.put(t.getId(), t);
                return t;
            }
        }
        throw new IllegalStateException("No spot available");
    }

    public BigDecimal exitVehicle(String ticketId) {
        Ticket t = activeTickets.remove(ticketId);
        if (t == null) throw new IllegalArgumentException("Unknown ticket");
        t.setExitTime(Instant.now());
        BigDecimal amount = feeStrategy.calculate(t, /* lookup type */ VehicleType.CAR);
        // free the spot lookup omitted
        return amount;
    }

    public void setFeeStrategy(FeeStrategy fs) { this.feeStrategy = fs; } // hot-swap pricing
}

How the patterns interact:

  • ParkingLot is the Singleton orchestrator.
  • SpotFactory builds spots — caller doesn’t new ParkingSpot(...) directly; swap to a sub-class hierarchy if needed.
  • FeeStrategy is hot-swappable — switch from HourlyFeeStrategy to FlatFeeStrategy without touching ParkingLot.
  • Concurrency: synchronized on assign/release per spot, ConcurrentHashMap for tickets.

Extensions to mention out loud: EV spots (new SpotType), reserved spots (new assign precondition), surge pricing (new FeeStrategy).


7. Rate Limiter LLD — Sanjay’s gold story

This maps directly to your AI Rate Limiter + omnichannel multi-tenant work. Lead with this. Patterns: Strategy (algorithm), Factory (algo by config), Singleton (registry), Decorator (metrics/logging wrapper).

Requirements to clarify out loud:

  • Per-tenant, per-API key, per-endpoint limits? (Sanjay: yes — multi-tenant).
  • Single-node or distributed? (Distributed → Redis-backed counter).
  • Algorithms supported? (Token bucket, sliding window, fixed window).
  • Action on limit breach? (Reject 429, queue, soft-throttle).
  • SLA: how strict on cluster-wide accuracy vs latency?
// ----- Core abstraction -----
public interface RateLimiter {
    /** @return true if request allowed; false if throttled */
    boolean tryAcquire(String tenantId, String key, int permits);
}

// ----- Per-tenant config -----
public class RateLimitConfig {
    private final int capacity;        // max tokens / window size
    private final int refillPerSecond; // tokens added per sec (TB)
    private final Duration window;     // window duration (SW/FW)
    private final Algorithm algorithm;
    // ctor + getters
}
public enum Algorithm { TOKEN_BUCKET, SLIDING_WINDOW, FIXED_WINDOW }

// ----- Redis-backed counter store -----
public interface CounterStore {
    long incrementAndGet(String key, Duration ttl);
    long get(String key);
    boolean compareAndSet(String key, long expected, long updated);
    Long evalLua(String script, List<String> keys, List<String> args); // atomic ops
}
public class RedisCounterStore implements CounterStore { /* Jedis/Lettuce impl */ }

// ----- Strategy 1: Token Bucket -----
public class TokenBucketLimiter implements RateLimiter {
    private final CounterStore store;
    private final Function<String, RateLimitConfig> configResolver; // per-tenant

    public TokenBucketLimiter(CounterStore s, Function<String, RateLimitConfig> r) {
        this.store = s; this.configResolver = r;
    }
    @Override
    public boolean tryAcquire(String tenantId, String key, int permits) {
        RateLimitConfig cfg = configResolver.apply(tenantId);
        String redisKey = "rl:tb:" + tenantId + ":" + key;
        // Lua script: atomically refill based on elapsed time, then decrement by `permits`
        Long allowed = store.evalLua(REFILL_AND_TAKE_LUA,
            List.of(redisKey),
            List.of(String.valueOf(cfg.getCapacity()),
                    String.valueOf(cfg.getRefillPerSecond()),
                    String.valueOf(permits),
                    String.valueOf(Instant.now().toEpochMilli())));
        return allowed != null && allowed == 1L;
    }
    private static final String REFILL_AND_TAKE_LUA = "..."; // see notes
}

// ----- Strategy 2: Sliding Window Log -----
public class SlidingWindowLimiter implements RateLimiter {
    private final CounterStore store;
    private final Function<String, RateLimitConfig> configResolver;
    public SlidingWindowLimiter(CounterStore s, Function<String, RateLimitConfig> r) {
        this.store = s; this.configResolver = r;
    }
    @Override
    public boolean tryAcquire(String tenantId, String key, int permits) {
        // ZADD timestamp; ZREMRANGEBYSCORE older than now-window; ZCARD; compare to capacity
        return true; // skeleton
    }
}

// ----- Strategy 3: Fixed Window -----
public class FixedWindowLimiter implements RateLimiter {
    private final CounterStore store;
    private final Function<String, RateLimitConfig> configResolver;
    public FixedWindowLimiter(CounterStore s, Function<String, RateLimitConfig> r) {
        this.store = s; this.configResolver = r;
    }
    @Override
    public boolean tryAcquire(String tenantId, String key, int permits) {
        RateLimitConfig cfg = configResolver.apply(tenantId);
        long bucket = System.currentTimeMillis() / cfg.getWindow().toMillis();
        String redisKey = "rl:fw:" + tenantId + ":" + key + ":" + bucket;
        long count = store.incrementAndGet(redisKey, cfg.getWindow());
        return count <= cfg.getCapacity();
    }
}

// ----- Factory -----
public class RateLimiterFactory {
    public static RateLimiter create(Algorithm algo, CounterStore store,
                                     Function<String, RateLimitConfig> resolver) {
        return switch (algo) {
            case TOKEN_BUCKET   -> new TokenBucketLimiter(store, resolver);
            case SLIDING_WINDOW -> new SlidingWindowLimiter(store, resolver);
            case FIXED_WINDOW   -> new FixedWindowLimiter(store, resolver);
        };
    }
}

// ----- Decorator: metrics -----
public class MetricsRateLimiter implements RateLimiter {
    private final RateLimiter delegate;
    private final MeterRegistry metrics;
    public MetricsRateLimiter(RateLimiter d, MeterRegistry m) { this.delegate = d; this.metrics = m; }
    @Override
    public boolean tryAcquire(String tenantId, String key, int permits) {
        boolean ok = delegate.tryAcquire(tenantId, key, permits);
        metrics.counter("rate_limiter", "tenant", tenantId, "result", ok ? "allowed" : "denied").increment();
        return ok;
    }
}

// ----- Singleton registry of per-tenant configs -----
public final class TenantConfigRegistry {
    private static final TenantConfigRegistry INSTANCE = new TenantConfigRegistry();
    private final Map<String, RateLimitConfig> configs = new ConcurrentHashMap<>();
    private TenantConfigRegistry() {}
    public static TenantConfigRegistry getInstance() { return INSTANCE; }
    public RateLimitConfig get(String tenantId)             { return configs.get(tenantId); }
    public void put(String tenantId, RateLimitConfig cfg)   { configs.put(tenantId, cfg); }
}

Talking points to drop in the interview:

  • Why Redis-backed? Distributed across N pods — local counters are wrong under load balancer.
  • Why Lua for token bucket? Refill + decrement must be atomic; round-trip is one shot.
  • Why Strategy? AI workloads (LLM inference) need different shapes — bursty token bucket for chat, fixed window for batch.
  • Multi-tenant? Key namespacing rl:<algo>:<tenant>:<endpoint>, config resolver pulls per-tenant limits from DB / cached.
  • Failure mode? If Redis is down — fail open or closed? Configurable per tenant.

This is your STAR story anchor: “At Jio, I built an AI Rate Limiter for our omnichannel platform. We had multi-tenant SaaS with wildly different SLAs — used Strategy to swap algorithms per tenant, Redis Lua for atomicity, and a decorator chain for metrics and circuit breaking.”


8. Common mistakes / red flags in LLD rounds

  • Over-engineering — 14 patterns for Tic-Tac-Toe. Use the minimum; name 1–2 patterns deliberately.
  • Missing abstractions — no interfaces, just concrete classes wired together. Always start with the interface.
  • instanceof chainsif (v instanceof Car) ... else if (v instanceof Truck) — replace with polymorphism or Strategy.
  • God classesParkingLot doing parking, billing, reporting, notifications. Split by SRP.
  • No thread-safety where it matters — two threads grab same spot, double-charging, lost updates. Mention synchronized / ConcurrentHashMap / Redis Lua even if you don’t fully implement.
  • Ignoring extensibility — when asked “what if we add EV?” don’t say “change the enum and the if-else.” Show OCP-clean extension.
  • Exposing mutable internals — returning List<ParkingSpot> directly. Wrap in Collections.unmodifiableList or expose query methods.
  • Mixing concerns — business logic in controllers, DB calls inside domain objects. Keep layers clean.
  • Skipping enums — strings for status ("ACTIVE", "PAID"). Use enums.
  • Not asking clarifying questions — diving into code at minute 1. Spend the first 5.
  • No trade-offs spoken — silent coding. Narrate every design choice.
  • Forgetting equals/hashCode for value objects used as map keys.
  • Returning null instead of Optional for “not found” cases.

9. Sources / further reading

Companion files in this folder:

  • ../01-DSA/top-questions.md — pattern-based DSA prep
  • ../02-HLD/top-questions.md — system design top questions
  • ../04-Behavioral/top-questions.md — STAR stories & LP

Prep cadence suggestion:

  • Week 1 — Parking Lot + Rate Limiter (cold-solve in 45 min, twice each)
  • Week 2 — Notification System + LRU Cache + Splitwise
  • Week 3 — BookMyShow + Elevator + File System
  • Week 4 — Mock with peer on shared IntelliJ, time-boxed 45 min