Chapter 1 · Lesson 2

Why Most Candidates Fail LLD Interviews

The four recurring mistakes that sink LLD interviews and the mental shift that fixes each one.

Learn the four recurring mistakes that sink LLD interviews — and the small mental shift that fixes each one.

🎯 Hook

Two engineers get the same prompt: "Design a ride-hailing matcher." Both write clean, working Java. One gets the offer; the other gets a "we'll be in touch."

The difference is almost never syntax. It's how they got to the code. The candidate who failed started typing class names in minute two. The one who passed spent four minutes naming the nouns and the roles in the problem, and only then wrote a line of code.

LLD interviews fail for boringly predictable reasons. In this lesson we autopsy the four most common killers — and you'll see a tiny before/after for each so the fix becomes muscle memory.

The four killers

Across thousands of LLD interviews, the same four anti-patterns show up over and over. Memorize them as the things to not do:

  1. Killer #1 — Jumping straight to code. Writing classes before you've identified the entities, their responsibilities, and the API. You end up coding the first idea instead of the right one.
  2. Killer #2 — Missing abstractions. Hard-coding behavior with if/else on a type or string, instead of modeling it as an interface with implementations.
  3. Killer #3 — Overengineering. Reaching for five design patterns, factories of factories, and a config DSL for a problem that needed three classes.
  4. Killer #4 — Ignoring extensibility. A design that works for today's requirement but forces you to rewrite existing classes the moment the interviewer says "now also support X."

Notice the tension: #2 and #3 pull in opposite directions. Great candidates abstract exactly the axis that will change — no more, no less. That judgment is the whole game.

flowchart LR A["Read the prompt"] --> B{"Jump to code?"} B -- "Yes (Killer 1)" --> X["Wrong model, no time to fix"] B -- "No: find nouns and roles" --> C{"Model the varying axis?"} C -- "Hard-coded if/else (Killer 2)" --> X C -- "Too many patterns (Killer 3)" --> X C -- "Right abstraction" --> D{"Open for extension?"} D -- "Must edit old code (Killer 4)" --> X D -- "Add a class, no edits" --> P["Offer"]

Killer #1 — Jumping straight to code

The fix is a 3-minute ritual before any class: list the entities (nouns), the actions (verbs), and the public API the caller wants. Say it out loud. Only then type.

Killer #2 — Missing abstractions

Before — pricing logic switches on an enum. Every new ride type forces you to edit this method (and you'll forget a branch).

enum RideType { POOL, REGULAR, PREMIUM }

class FareCalculator {
    double fare(RideType type, double km) {
        if (type == RideType.POOL)    return km * 0.8;
        if (type == RideType.REGULAR) return km * 1.0;
        if (type == RideType.PREMIUM) return km * 1.5 + 50;  // base fee
        throw new IllegalArgumentException("Unknown type: " + type);
    }
}

After — the thing that varies (the pricing rule) becomes an abstraction. Adding a ride type means adding a class, never touching FareCalculator.

interface PricingStrategy {
    double fareFor(double km);
}

final class PoolPricing implements PricingStrategy {
    public double fareFor(double km) { return km * 0.8; }
}

final class RegularPricing implements PricingStrategy {
    public double fareFor(double km) { return km * 1.0; }
}

final class PremiumPricing implements PricingStrategy {
    private static final double BASE_FEE = 50;
    public double fareFor(double km) { return km * 1.5 + BASE_FEE; }
}

final class FareCalculator {
    double fare(PricingStrategy pricing, double km) {
        return pricing.fareFor(km);   // no branching, ever
    }
}

The if/else chain on a type is the single loudest "missing abstraction" smell an interviewer listens for.

Killer #3 — Overengineering

Before — a candidate "designs for the future" with a factory, a builder, and an abstract base, to produce one kind of object.

abstract class AbstractNotificationFactory {
    abstract NotificationBuilder newBuilder();
}

class EmailNotificationFactory extends AbstractNotificationFactory {
    NotificationBuilder newBuilder() { return new EmailNotificationBuilder(); }
}

class EmailNotificationBuilder {
    private String to;
    private String body;
    EmailNotificationBuilder to(String t)   { this.to = t; return this; }
    EmailNotificationBuilder body(String b) { this.body = b; return this; }
    Notification build() { return new EmailNotification(to, body); }
}
// ...and we only ever send emails.

After — model what the requirement actually asked for. You can always introduce a strategy interface when a second channel appears.

record Notification(String to, String body) {}

final class EmailSender {
    void send(Notification n) {
        // hand off to SMTP client
        System.out.println("Email to " + n.to() + ": " + n.body());
    }
}

Rule of thumb: abstract on the second occurrence, not the first. Patterns are a response to real variation, not a way to look senior.

Killer #4 — Ignoring extensibility

This is the inverse of #3: under-abstracting on the axis the interviewer is about to stress. They will always add a requirement. A design passes when the new requirement is "add a class," and fails when it's "rewrite this method." The class diagram below shows the extensible shape from Killer #2 — a new ride type plugs in with zero edits to existing code.

classDiagram class FareCalculator { +fare(PricingStrategy, double) double } class PricingStrategy { <<interface>> +fareFor(double) double } class PoolPricing class RegularPricing class PremiumPricing class SurgePricing FareCalculator ..> PricingStrategy : uses PricingStrategy <|.. PoolPricing PricingStrategy <|.. RegularPricing PricingStrategy <|.. PremiumPricing PricingStrategy <|.. SurgePricing

When the interviewer says "now add surge pricing," you write one SurgePricing class and you're done. That moment — adding a feature without editing old code — is what earns the offer.

✍️ Exercise

Here's a snippet a candidate wrote for a parking lot. Identify which killer(s) it commits, then sketch the fix in two sentences.

class ParkingLot {
    int park(String vehicleType, int spotId) {
        if (vehicleType.equals("CAR"))   return spotId * 2;
        if (vehicleType.equals("BIKE"))  return spotId;
        if (vehicleType.equals("TRUCK")) return spotId * 3;
        return -1;
    }
}

Questions to answer:

  1. Which killer does the String-based if/else reveal?
  2. What happens when the interviewer adds an "ELECTRIC" vehicle that needs a charging spot?
  3. What abstraction makes this open for extension? (Hint: think about what a Vehicle knows about itself.)

Target answer: It's Killer #2 (missing abstraction) leading to Killer #4 (not extensible). Replace the string switch with a Vehicle interface exposing spotsNeeded() (or a VehicleType enum carrying the multiplier), so a new type is a new class — no edits to ParkingLot.park.

🎤 Interview connection & traps

Interviewers grade your process as much as your code. Each killer maps directly to a thing they're scoring:

  • Requirements clarification → catches Killer #1. Always restate the problem and ask 2–3 scoping questions first.
  • Identifying the varying axis → catches #2 and #4. Say out loud: "The part likely to change is the pricing rule, so I'll make that an interface."
  • Pragmatism → catches #3. Verbalize "I'll keep this concrete for now and extract an interface if we add a second channel."

⚠️ Trap: Reciting pattern names ("I'll use Strategy and Factory and Observer") to sound senior. Interviewers hear it as overengineering. Name a pattern only when the requirement forces it, and justify it in one sentence.

⚠️ Trap: Silent coding. If you design the perfect class hierarchy but say nothing, the interviewer can't grade your reasoning. Narrate the entities and the changing axis before you type.

⚠️ Trap: Treating "extensible" as "abstract everything." Abstract the one axis the requirements point at; abstracting the rest is just Killer #3 in disguise.

Lesson Summary
  • Four killers, in order of frequency: jumping to code, missing abstractions, overengineering, and ignoring extensibility — and #2/#3 are opposite failures of the same judgment call.
  • The litmus test: when the interviewer adds a requirement, a good design adds a class; a bad one rewrites a method. Replace type-based if/else with an interface on the axis that varies.
  • Process is graded: clarify first, narrate the varying axis, and abstract on the second occurrence — not to look senior, but because real variation demands it.