Failure Modes: Over-Engineering & Pattern-Forcing
A small set of mistakes accounts for most weak LLD answers, and they tend to appear in a consistent order. This lesson describes the six most common failure modes, explains why each one lowers the score, and shows a corrected approach for each.
Diving in before clarifying requirements
Opening an editor before asking a single question is a common early mistake. A candidate who
begins with "I'll define a Notification class" the moment the prompt lands has not yet
established what they are building. Interviewers provide one-sentence prompts intentionally —
the prompt is incomplete, and the questions a candidate asks reveal engineering judgment.
Clarifying requirements is itself design work. Whether notifications are sent via email, SMS, or push; whether the caller cares about delivery confirmation; and whether retries are in scope — each answer either removes a class that would otherwise be built unnecessarily, or surfaces a constraint that changes the design.
Before naming any entity, restate the prompt in your own words and ask two or three targeted questions. Write an in-scope list and an out-of-scope list. Only then begin writing classes.
Pattern-forcing
The second failure mode is reaching for a design pattern when a plain function or a plain
field would do. Candidates who have studied patterns sometimes feel pressure to demonstrate
them, so they install a NotificationFactory because the problem involves creating objects,
or a Singleton because there is one notification service, or an Observer because events
are mentioned. Each of these can be right in context. None of them is right by default.
(Factory, Singleton, Observer, and the rest are previewed here only as cautionary examples;
they are taught properly in Module 4 — you do not need to know them yet.)
Adding an unnecessary pattern usually costs more than omitting one. A Factory wrapping a two-line constructor adds a class and a layer of indirection without removing any duplication. A pattern is justified only when it removes a specific problem: duplicated creation logic, the need for a single shared instance, or a publisher that should not know its subscribers. If that problem cannot be named, the pattern does not belong.
Writing the simplest code that satisfies the requirements listed in Step 1 is the correct baseline. If a natural smell surfaces during implementation, name it and introduce the pattern at that point. Most clean designs use zero or one pattern.
A notification service that dispatches messages by channel — email, SMS, push — illustrates the difference concretely. The code below shows pattern-forcing alongside a plain alternative.
1# Bad: Factory added with no smell to remove.
2# There is one creation site, one line of logic per channel,
3# and no duplication. The factory is pure ceremony.
4class NotificationSenderFactory:
5 @staticmethod
6 def create(channel: str) -> "NotificationSender":
7 if channel == "email":
8 return EmailSender()
9 elif channel == "sms":
10 return SmsSender()
11 elif channel == "push":
12 return PushSender()
13 raise ValueError(f"unknown channel: {channel}")
14
15class NotificationService:
16 def send(self, user_id: str, message: str, channel: str) -> None:
17 sender = NotificationSenderFactory.create(channel)
18 sender.send(user_id, message)
1# Good: a plain dict maps channel to sender — zero pattern overhead.
2# If creation logic grows complex or senders need pooling,
3# that is the moment to consider a factory.
4class NotificationService:
5 def __init__(self) -> None:
6 self._senders: dict[str, "NotificationSender"] = {
7 "email": EmailSender(),
8 "sms": SmsSender(),
9 "push": PushSender(),
10 }
11
12 def send(self, user_id: str, message: str, channel: str) -> None:
13 sender = self._senders.get(channel)
14 if sender is None:
15 raise ValueError(f"unsupported channel: {channel}")
16 sender.send(user_id, message)
The god class
The third failure mode is the god class: one class that owns every piece of state in the system, with public mutable fields that any caller can reach into and change. It usually appears when a candidate skips Step 2 (entities and relationships) and starts writing directly in a single file. Everything ends up on the same object because there was never a moment to ask which noun should own which piece of state.
A god class is a maintenance problem in production and a design problem in an interview. It tells the interviewer that the candidate does not think in terms of encapsulation or single responsibility. The fix is mechanical: at Step 2, identify the nouns in the requirements, assign one responsibility to each noun, and keep the state for that responsibility private behind methods.
Premature abstraction
The fourth failure mode is introducing interfaces and configurability for requirements
that do not exist. You are not asked to design for every future channel, every future
payment gateway, or every future pricing model — you are asked to design for the
requirements you listed in Step 1. Adding an IPaymentProcessor interface, a
PaymentProcessorFactory, and a configuration layer when there is only one payment
processor in scope is YAGNI — You Aren't Gonna Need It — and adds machinery no requirement uses.
Extensibility and over-abstraction are different. Extensibility means the design can absorb a real follow-up without a rewrite; over-abstraction means it pre-builds extension points for requirements that do not exist. Build for what was asked. In Step 6 (Extensibility), show that a natural follow-up fits — but do not implement the follow-up unprompted.
Scope creep
The fifth failure mode is gold-plating instead of finishing the core. Time is fixed at
roughly forty minutes. A candidate who spends fifteen minutes on a polished retry
mechanism for notifications while the core Checkout flow is unimplemented has
misread the problem. The interviewer cannot grade what is not there.
The discipline from Step 4 applies directly: implement the happy path first, add edge-case guards second, sketch the rest as stubs with a brief comment. If time is short, a working skeleton with clear method signatures scores higher than a half-finished tangle of edge cases.
A checkout flow shows this clearly. The following code demonstrates scope creep alongside a focused alternative.
1# Bad: gold-plating retry logic and idempotency keys while
2# the Checkout class itself is still a stub.
3class PaymentGateway:
4 def charge(
5 self,
6 amount: int,
7 card_token: str,
8 idempotency_key: str,
9 max_retries: int = 3,
10 backoff_base: float = 0.5,
11 ) -> "ChargeResult":
12 for attempt in range(max_retries):
13 try:
14 return self._attempt_charge(amount, card_token, idempotency_key)
15 except TransientError:
16 time.sleep(backoff_base * (2 ** attempt))
17 raise PaymentError("all retries exhausted")
18
19class Checkout:
20 pass # TODO: not started — time ran out
1# Good: happy path for Checkout is complete and correct.
2# PaymentGateway.charge() is a thin stub — the interviewer can see
3# its signature and knows retries can be added in Step 6.
4class PaymentGateway:
5 def charge(self, amount: int, card_token: str) -> "ChargeResult":
6 # stub: calls external processor, returns result
7 ...
8
9class Checkout:
10 def __init__(self, cart: "Cart", gateway: PaymentGateway) -> None:
11 self._cart = cart
12 self._gateway = gateway
13
14 def complete(self, card_token: str) -> "Order":
15 total = self._cart.total()
16 result = self._gateway.charge(total, card_token)
17 if not result.success:
18 raise PaymentError(result.error)
19 return Order(items=self._cart.items(), total=total)
Going silent
Going quiet for several minutes while the interviewer waits is a distinct failure mode, and one that does not appear in the code at all. LLD interviews are collaborative. When the interviewer steers the conversation — "what if we need to support a third payment method?" — that is either filling in a requirement that was missed or checking whether the design can absorb a change without a rewrite. In either case, the appropriate response is to engage immediately: restate what was heard, identify what changes in the design, and narrate the adjustment.
Communication is a graded dimension in every LLD rubric. An interviewer who cannot follow
the reasoning cannot award credit for it, regardless of the code quality. Narrating each
significant decision in a sentence keeps the reasoning visible. "I'm making PaymentMethod
an interface here because the requirements mention two processors — that's the natural
extension point" states the reasoning in one sentence, so the interviewer can follow it and
credit it.
Keeping the six straight
The six failure modes form a natural arc through the interview timeline.
| # | Failure mode | When it strikes |
|---|---|---|
| 1 | No requirements | First two minutes |
| 2 | Pattern-forcing | Entity step |
| 3 | God class | Class design |
| 4 | Premature abstraction | Class design |
| 5 | Scope creep | Implementation |
| 6 | Going silent | Throughout |
The delivery framework from the previous lesson addresses the first five structurally. Following its steps in order — requirements, then entities, then class design, then implementation — makes each of those failure modes harder to fall into. The sixth is addressed through practice: narrating decisions aloud, engaging with every steer from the interviewer, and treating the session as collaborative rather than a solo exercise.