Loading content...
It's Monday morning. You're staring at a Jira ticket that reads: "Refactor payment processing to support multiple currencies." The existing system has been in production for three years, handles millions of transactions daily, and was designed before anyone imagined international expansion.
You won't solve this in 45 minutes. You won't even solve it in a week. Over the next several months, you'll study the existing codebase, collaborate with five different teams, navigate conflicting requirements from stakeholders, make incremental changes while keeping production stable, and ultimately deliver a solution that nobody could have designed from scratch.
This is real-world LLD. It's messy, collaborative, iterative, and nothing like an interview. Understanding this reality prepares you for the work that actually awaits you in industry.
By the end of this page, you will understand how production LLD differs fundamentally from interview LLD, why iterative design produces better outcomes than waterfall approaches, how team dynamics shape design decisions, and why maintenance concerns outweigh initial elegance in professional contexts.
In interviews, you design once and never touch it again. In production, design is a continuous activity that spans the entire lifecycle of a system.
Why Iteration Is Necessary:
The myth of the perfect upfront design is exactly that—a myth. Real engineering is about designing systems that can evolve gracefully.
| Dimension | Interview LLD | Real-World LLD |
|---|---|---|
| Timeline | 30-60 minutes | Weeks to months |
| Iterations | One and done | Continuous refinement |
| Requirements | Fixed (after clarification) | Evolving constantly |
| Team Size | Solo | Multiple engineers and stakeholders |
| Legacy Code | Greenfield (usually) | Brownfield (usually) |
| Production Concerns | Abstract consideration | Primary driver |
| Feedback Loop | Interviewer hints | Users, metrics, incidents |
| Documentation | Optional | Essential |
| Testing | Often skipped | Non-negotiable |
| Reversibility | Low stakes | Migrations are expensive |
The Iteration Cycle:
Production design follows a predictable pattern that repeats throughout development:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Understand │────▶│ Design │────▶│ Implement │
└─────────────┘ └─────────────┘ └─────────────┘
▲ │
│ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Refine │◀────│ Measure │◀────│ Deploy │
└─────────────┘ └─────────────┘ └─────────────┘
Each cycle produces learning that informs the next. The system gets better not through brilliant initial design, but through intelligent response to feedback.
Production engineering values 'good enough to ship and evolve' over 'perfect but never finished.' The best engineers know when to stop polishing and start iterating with real-world feedback.
Interview LLD is a solo activity. Real-world LLD is fundamentally collaborative. The quality of your design depends not just on your individual skills, but on how well you work with others.
Stakeholders in Real-World Design:
Every significant design decision involves multiple perspectives:
Design Reviews:
Most organizations formalize collaboration through design reviews. These serve multiple purposes:
The Design Document:
Unlike interviews where you think aloud, production design is documented in written form. A typical design document includes:
123456789101112131415161718192021222324252627282930313233343536373839404142
# Feature: Multi-Currency Payment Processing ## OverviewBrief description of what we're building and why. ## BackgroundContext about the current system and what prompted this change. ## Goals and Non-GoalsGOALS:- Support transactions in EUR, GBP, JPY, CAD- Maintain real-time exchange rate accuracy- Zero downtime migration NON-GOALS:- Cryptocurrency support (future consideration)- Historical exchange rate queries (separate project) ## Proposed Design### Component Architecture[Diagrams and descriptions of the new component structure] ### Class Design[Key classes, interfaces, and their relationships] ### API Changes[New or modified endpoints/interfaces] ### Data Model[Database schema changes] ## Alternatives ConsideredWhat other approaches we explored and why we rejected them. ## Migration PlanHow we transition from current to new without breaking production. ## Testing StrategyHow we verify correctness at each stage. ## Open QuestionsThings we still need to figure out.Writing forces clarity. Many design flaws are discovered during the documentation process, not during review. If you can't explain your design in writing, you probably don't understand it well enough to implement it.
Interview problems usually present greenfield scenarios: "Design a parking lot system from scratch." Production work is almost never greenfield.
In reality, you inherit codebases with:
The Brownfield Challenge:
Brownfield development—modifying existing systems—requires different skills than greenfield design:
The Strangler Fig Pattern:
One of the most important real-world design patterns is the Strangler Fig—a strategy for replacing legacy systems incrementally:
This pattern lets you improve design over time without the risk of a big-bang rewrite.
Reading Code as a Design Skill:
In brownfield environments, understanding existing code is as important as writing new code. Effective code reading involves:
Every engineer looks at legacy code and thinks: 'I could design this better from scratch.' This is almost always a trap. The legacy system encodes hard-won knowledge about edge cases, integration quirks, and failure modes. Rewrites frequently ship late and miss critical functionality the old system handled.
The interview ends when you finish designing. Production systems live for years or decades. This changes everything about how you should think about design.
The Maintenance Perspective:
In production, the total lifecycle cost of code is dominated by maintenance, not initial development:
| Phase | Interview Weight | Production Weight |
|---|---|---|
| Initial Design | 100% | ~20% |
| Implementation | 0% (not evaluated) | ~20% |
| Maintenance | 0% (not evaluated) | ~60% |
This means optimizing for maintainability is more important than optimizing for cleverness.
Design for the Reader:
Code is read far more often than it's written. Real-world design prioritizes:
Technical Debt as Reality:
Every project accumulates technical debt—design compromises made for expedience that create future maintenance burden. Production LLD requires managing debt explicitly:
The key insight is that some debt is acceptable and even strategic. The skill is knowing how much debt to take and when to pay it down.
"Leave the campsite cleaner than you found it." When working in existing code, make small improvements beyond your immediate task. Over time, this compounds into significant quality improvement without dedicated refactoring sprints.
Interview LLD rarely touches on production concerns—topics like deployment, monitoring, and operational visibility. In real-world design, these concerns often drive the architecture as much as functional requirements.
Categories of Production Concerns:
| Category | Key Questions | Design Impact |
|---|---|---|
| Observability | How will we know if it's working? How will we debug problems? | Logging interfaces, metric hooks, trace correlation IDs |
| Operability | How will ops teams interact with this system? | Admin interfaces, configuration injection, graceful degradation |
| Deployability | How do we ship changes safely? | Feature toggles, backward compatibility, rollback support |
| Testability | How do we verify correctness at each level? | Dependency injection, interface seams, test data factories |
| Security | How do we protect against threats? | Input validation, access control boundaries, audit logging |
| Performance | How will this behave under load? | Caching strategies, connection pooling, async processing boundaries |
Design for Observability:
You can't improve what you can't measure. Production design embeds observability from the start:
// Interview code
public void processPayment(Payment payment) {
validatePayment(payment);
chargeCard(payment);
updateRecord(payment);
}
// Production code
public void processPayment(Payment payment) {
Span span = tracer.buildSpan("processPayment").start();
try {
metrics.counter("payments.attempted").inc();
validatePayment(payment);
span.log("validation_complete");
chargeCard(payment);
span.log("charge_complete");
updateRecord(payment);
metrics.counter("payments.succeeded").inc();
span.setTag("result", "success");
} catch (Exception e) {
metrics.counter("payments.failed").inc();
span.setTag("error", true);
span.log("error: " + e.getMessage());
throw e;
} finally {
span.finish();
}
}
The production code is longer and more complex—but it's engineered for visibility. When something goes wrong at 3 AM, you'll thank past-you for the telemetry.
Excellent engineers think about the on-call engineer who will debug their code. They design systems that fail gracefully, provide clear error messages, and make the right action obvious when something goes wrong.
In interviews, feedback comes from the interviewer's hints and expressions. In production, feedback comes from multiple channels, each revealing different aspects of your design's quality.
Production Feedback Channels:
Learning from Incidents:
Production incidents are the most valuable (and painful) source of design feedback. A mature engineering culture conducts blameless post-mortems that ask:
These insights feed directly back into design improvements. The best production engineers actively seek this feedback rather than avoiding it.
Continuous Improvement:
Real-world LLD isn't a one-time activity—it's an ongoing dialogue between design and reality. Every deployment teaches you something. Every incident reveals an assumption. Every code review shares perspective.
The engineer who designs the best systems isn't necessarily the smartest at initial design. They're the ones who learn fastest from feedback and improve most relentlessly.
Don't wait for problems to find you. Actively seek feedback: request thorough code reviews, add metrics proactively, and periodically ask 'what would make this system easier to work with?' The discomfort of upfront feedback is nothing compared to the cost of production failures.
Interview LLD typically focuses on core business logic. Production LLD must also address cross-cutting concerns—aspects that affect multiple components and don't fit neatly into a single class.
Common Cross-Cutting Concerns:
Design Patterns for Cross-Cutting Concerns:
Managing these concerns cleanly requires specific patterns that interview LLD rarely covers:
| Pattern | Use Case | How It Works |
|---|---|---|
| Decorator | Adding behavior to methods transparently | Wraps objects to add logging, caching, auth checks |
| Interceptor/Middleware | Pre/post processing of requests | Chain of handlers that each do one thing |
| Aspect-Oriented Programming | Separating concerns completely | Weaves behavior into compiled code |
| Configuration Objects | Externalizing settings | Inject config objects rather than hardcoding values |
| Abstract Factory | Environment-specific implementations | Produce different objects based on context |
| Service Locator/DI Container | Managing dependencies | Central registry for object resolution |
Example: Consistent Error Handling Architecture
Without a cross-cutting strategy, every component invents its own error handling:
// Inconsistent - each class does its own thing
class OrderService {
void placeOrder() {
try { ... }
catch (Exception e) {
log.error("Order failed", e);
throw new RuntimeException(e); // Lost context!
}
}
}
class PaymentService {
void processPayment() {
try { ... }
catch (Exception e) {
e.printStackTrace(); // Goes nowhere in production!
return null; // Silent failure!
}
}
}
With a cross-cutting architecture, behavior is consistent and centralized:
// Consistent - error handling is standardized
abstract class DomainException extends RuntimeException {
abstract ErrorCode getCode();
abstract Map<String, Object> getContext();
}
class OrderServiceException extends DomainException { ... }
class PaymentServiceException extends DomainException { ... }
// Global error handler (in middleware/interceptor)
class GlobalErrorHandler {
void handle(DomainException e) {
logger.error("Domain error",
"code", e.getCode(),
"context", e.getContext());
metrics.counter("errors." + e.getCode()).inc();
alertIfCritical(e);
}
}
This architecture ensures every error gets logged, measured, and potentially alerted—without individual services reimplementing the wheel.
Senior engineers think in terms of platforms, not just features. They build infrastructure that makes the right thing easy—so every team automatically gets consistent logging, error handling, and observability without reinventing them.
Real-world LLD is a fundamentally different discipline than interview LLD. Where interviews reward quick thinking and elegant initial designs, production rewards sustainable evolution, team collaboration, and maintenance awareness.
What's Next:
Now that we understand both interview and real-world contexts, we'll examine what interviewers actually expect—the specific signals they look for and how to demonstrate competency within interview constraints while building skills that transfer to production work.
You now understand how production LLD fundamentally differs from interviews: iterative rather than one-shot, team-based rather than solo, maintenance-focused rather than creation-focused. These insights prepare you for real engineering work while informing better interview preparation.