LLD guidelines

LLD guidelines & STAR narration

KISS, DRY, YAGNI, separation of concerns, Law of Demeter, SOLID for interviews, STAR as LLD narration, and when to reach for a basic pattern.

Three layers of a strong answer

01OOP

Model with care

02Guidelines

Decide cleanly

03STAR

Narrate clearly

04Patterns

Only when earned

Strong LLD answers combine OOP core for modeling, guidelines for decisions, STAR for spoken structure, and a few basic patterns when variation appears. Interviewers care that you apply the lessons — not that you recite acronyms.
LLD guidelines
KISS · DRY · YAGNI · Tell Don’t Ask · Compose.

General principles that carry most interviews

If you only remember three: KISS, DRY, YAGNI. Add separation of concerns and Law of Demeter for cleaner APIs.
KISS. The simplest solution that works is usually right. Prefer a conditional over Strategy until variation is real. Prefer one clear class over a premature split. Over-engineering to “show patterns” is the most common LLD foul. Add complexity when simplicity stops working.
DRY. Pull repeated logic into one place — but don’t merge code that only looks similar. DRY fights KISS sometimes; seniors name the tradeoff: “I’ll keep validation on User for now; if it appears three more times, we extract a validator.”
YAGNI. Build what’s needed now. Don’t invent valet parking and EV chargers unless the prompt asks. Design so extension is possible; implement only today’s requirements. When they ask “how would you extend,” that’s when you talk about the future.
Separation of concerns. UI shouldn’t own game rules; business logic shouldn’t know storage format.
# Bad: display + input + rules in one method
def play(self):
    while True:
        print(self.board)
        r, c = map(int, input().split())
        self.board[r][c] = "X"
        if diagonal_win(self.board):
            print("Winner!")
            break


# Good: orchestrate collaborators
def play(self):
    while not self.board.has_winner():
        self.display.render(self.board)
        move = self.input.get_next_move()
        self.board.make_move(move)
    self.display.show_winner(self.board.winner())
// Bad: display + input + rules in one method
void play() {
    while (true) {
        System.out.println(board);
        // read r, c from input
        board[r][c] = "X";
        if (diagonalWin(board)) {
            System.out.println("Winner!");
            break;
        }
    }
}

// Good: orchestrate collaborators
void play() {
    while (!board.hasWinner()) {
        display.render(board);
        Move move = input.getNextMove();
        board.makeMove(move);
    }
    display.showWinner(board.winner());
}
Law of Demeter (least knowledge). Talk to immediate collaborators — don’t chain through strangers: order.customer.address.zip. Prefer order.customer_zip() or a method that does the work. Fluent builders that return self are fine; leaking internal structure across types is not.

SOLID — use when the problem calls for it

SOLID comes from an era of deep Java hierarchies. Modern languages lean composition and simpler shapes. Don’t break KISS by forcing SOLID theater. Apply when it clarifies the design.
SRP. One reason to change. Split Report content, PDF printing, and file I/O into separate types.
OCP. Open for extension, closed for modification — usually via interfaces so new payment types don’t edit a giant if/else.
class PaymentMethod(Protocol):
    def process(self, amount: float) -> None: ...


class PaymentProcessor:
    def process(self, method: PaymentMethod, amount: float) -> None:
        method.process(amount)
# Add CryptoPayment without touching PaymentProcessor
interface PaymentMethod {
    void process(double amount);
}

class PaymentProcessor {
    void process(PaymentMethod method, double amount) {
        method.process(amount);
    }
}
// Add CryptoPayment without touching PaymentProcessor
LSP. Subtypes must honor the parent contract. Penguin.fly() throwing is a smell — split flying capability into its own interface.
ISP. Prefer small interfaces. Don’t force Robot to implement eat and sleep.
DIP. Depend on abstractions. Inject MessageSender; don’t new EmailSender() inside the service. (Dependency injection is a technique; DIP is the principle.)
class NotificationService:
    def __init__(self, sender: MessageSender):
        self._sender = sender

    def notify(self, message: str) -> None:
        self._sender.send(message)
class NotificationService {
    private final MessageSender sender;

    NotificationService(MessageSender sender) {
        this.sender = sender;
    }

    void notify(String message) {
        sender.send(message);
    }
}

STAR as LLD narration

STAR for LLD
Situation · Task · Action · Result — spoken structure for design.
  • Situation — restated requirements and out-of-scope.
  • Task — “Design a maintainable object model for X.”
  • Action — entities, APIs, key methods, tradeoffs.
  • Result — scenario walkthrough + how a follow-up lands.

Pattern instincts (details next)

Patterns are names for structures you create when principles lead there. Most interview-ready designs use zero, one, or two. Forcing three+ usually means over-engineering.
  • Variation in behavior at runtime → Strategy
  • Many listeners to one event → Observer
  • Behavior depends on complex state → State machine
  • Caller shouldn’t pick concrete type → Factory
  • Optional stacked behaviors → Decorator
  • Orchestrator hiding internals → Facade (often unnamed)
Full walkthroughs: design patterns for LLD.

Worked mini-example: STAR for a parking concurrency twist

# What you say
# "Situation: concurrent enter. Task: one spot one ticket.
#  Action: critical section around check-then-act.
#  Result: second enter gets lot-full instead of corrupt occupancy."

Narration anti-patterns

  • STAR with no Action — only vibes.
  • Guidelines as wallpaper (“I always use clean code”).
  • Pattern instinct that ships unused interfaces.
  • Blaming languages instead of invariants.

Before you name a pattern

  1. What varies? (algorithm, product type, output destination)
  2. What stays stable? (orchestrator API)
  3. Is the variation present in v1 or only a likely twist?
  4. Can I show the seam in one sentence?

Common interview pitfalls

These mistakes show up constantly on this prompt. Name the trap, then show the fix in your design — don’t wait for the interviewer to catch you.

  • STAR with empty Action.
  • Guidelines that never constrain a decision.
  • Pattern instinct that creates unused interfaces.
  • Talking longer than the design is worth.
  • No concrete example tying STAR to a class.

Interview script (say this)

Read this once out loud before a mock. It’s the spine of a strong answer — not a script to recite robotically.

  1. I narrate with STAR when answering twists or past design prompts.
  2. Guidelines: prefer enums over trees; keep orchestrators thin; cents not floats.
  3. Pattern instinct: name the variation first, pattern second.
  4. Example: rate limiter algorithm varies → Strategy + Factory.
  5. I won’t introduce Observer until someone cares about events.
  6. If stuck, I restate invariant and who owns it.
  7. I keep answers short enough to leave time for code.
  8. I invite the interviewer to pick the twist they care about.

Extra verification traces

Walk these three traces on the board. If you can narrate them cleanly, your implementation section usually follows.

Staff-level follow-ups

At staff+, they twist the prompt. Answer in one sentence that names the seam — don’t redesign the whole board.

  • Behavioral interview crossover? — Same STAR; swap parking for a shipped system story.
  • Too many guidelines? — Keep five personal rules max.
  • Pattern disagreement? — Show trade-off; accept interviewer’s preference and adapt.

← Lattice