How to write clean code in LLD interviews
Write code that interviewers love: meaningful names, small focused methods, clear structure, and comments that explain decisions, not syntax.
You have twenty minutes left in the interview. Your parking lot system works. But the interviewer is frowning. Your parkVehicle() method is 45 lines long. There is a variable called x. There is a comment that says // increment counter above counter++. Your code works, but the interviewer already decided against you.
Clean code is not about perfection. It is about signal. Every line you write tells the interviewer something about how you think, how you communicate through code, and whether other engineers can maintain what you build. In a 45-minute LLD round, clean code is the difference between "works but messy" and "hire."
This guide gives you the concrete rules, all with before-and-after Java examples you can start using immediately.
Why clean code matters
Three reasons. First, readability. The interviewer is not running your code, they are reading it. If they have to squint at a variable name or re-read a method three times, you are burning their goodwill.
Second, maintainability. Clean code shows you think about the engineer who reads this six months from now. In a real codebase, that engineer is often you.
Third, interview signal. Interviewers use code quality as a proxy for seniority. Juniors make it work. Mid-levels make it clean. Seniors make it obvious. I have seen candidates lose offers not because their design was wrong, but because their code looked like a first draft.
For your interview: clean code is free points. It costs you nothing extra once the habits are in place.
Naming things
Naming is the most visible clean code skill. The interviewer sees your names before they understand your logic. Bad names force re-reading. Good names make the code read like prose.
Classes: nouns, Methods: verbs
| Type | Bad | Good | Why |
|---|---|---|---|
| Class | Manager | ParkingLotService | What does it manage? Be specific. |
| Class | Helper | FareCalculator | Helpers are a code smell. Name the job. |
| Method | spot() | assignSpot() | What about the spot? Find it? Create it? |
| Method | check() | isSpotAvailable() | Boolean methods start with is, has, can. |
| Method | do() | calculateFare() | "Do" is meaningless. Name the action. |
Variables: descriptive, not abbreviated
The time you save typing cnt instead of counter costs ten seconds every time someone reads it. In an interview, the reader is the person deciding your future.
// β Cryptic
int n = spots.size();
for (int i = 0; i < n; i++) {
if (spots.get(i).getT() == 0) { ... }
}
// β
Descriptive
int totalSpots = spots.size();
for (int i = 0; i < totalSpots; i++) {
if (spots.get(i).getType() == SpotType.COMPACT) { ... }
}
Constants: UPPER_SNAKE_CASE
// β Magic numbers buried in logic
if (duration > 24) { fare = 500; }
// β
Named constants
private static final int MAX_PARKING_HOURS = 24;
private static final int FLAT_DAILY_RATE = 500;
if (duration > MAX_PARKING_HOURS) { fare = FLAT_DAILY_RATE; }
Interview tip: name things as you go
Do not name variables temp planning to rename later. You will not have time. Name it right the first time. If you cannot think of a good name, that is a sign you do not fully understand what the variable represents.
The rule of thumb: if someone can understand what a variable holds, what a method does, and what a class represents without reading the implementation, your names are good.
Small methods
Long methods are the most common interview code smell. A 40-line method forces the interviewer to hold the entire thing in working memory. A 10-line method is self-contained, testable, and readable.
The rule: one responsibility per method
Each method should do exactly one thing. If you find yourself writing a comment to separate "sections" inside a method, those sections should be separate methods.
Before vs after
// β One method does validation, search, assignment, and logging.
public ParkingTicket parkVehicle(Vehicle vehicle) {
if (vehicle == null) throw new IllegalArgumentException("Vehicle is null");
if (vehicle.getLicensePlate() == null || vehicle.getLicensePlate().isBlank())
throw new IllegalArgumentException("No license plate");
ParkingSpot found = null;
for (ParkingSpot spot : spots) {
if (!spot.isOccupied() && spot.getType().fits(vehicle.getType())) {
found = spot; break;
}
}
if (found == null) throw new NoAvailableSpotException(vehicle.getType());
found.setOccupied(true);
found.setVehicle(vehicle);
ParkingTicket ticket = new ParkingTicket(
UUID.randomUUID().toString(), vehicle, found, Instant.now());
activeTickets.put(ticket.getId(), ticket);
System.out.println("Parked " + vehicle.getLicensePlate() + " in " + found.getId());
return ticket;
}
// β
Each method has one job. The orchestrator reads like a checklist.
public ParkingTicket parkVehicle(Vehicle vehicle) {
validateVehicle(vehicle);
ParkingSpot spot = findAvailableSpot(vehicle.getType());
return assignVehicleToSpot(vehicle, spot);
}
private void validateVehicle(Vehicle vehicle) {
if (vehicle == null) throw new IllegalArgumentException("Vehicle is null");
if (vehicle.getLicensePlate() == null || vehicle.getLicensePlate().isBlank())
throw new IllegalArgumentException("No license plate");
}
private ParkingSpot findAvailableSpot(VehicleType type) {
return spots.stream()
.filter(s -> !s.isOccupied() && s.getType().fits(type))
.findFirst()
.orElseThrow(() -> new NoAvailableSpotException(type));
}
I aim for 5 to 15 lines per method. If a method hits 20 lines, look for extraction opportunities. This discipline communicates seniority.
Meaningful structure
Clean code is not just clean methods. It is clean organization. Where you put classes and how they relate sends signals to the interviewer.
Package organization
In an LLD interview, you do not need Maven multi-module builds. But grouping your classes by domain responsibility shows architectural thinking.
Three rules for package structure in interviews:
- model/ holds domain entities and enums. No logic beyond validation.
- service/ holds orchestration. Services call strategies, repositories, validators.
- strategy/ holds pluggable algorithms. This is where design patterns live.
You do not need physical folders in an interview. Just prefix class names or add a comment: // model. The interviewer sees you think in layers.
Class cohesion
Every class should have a single reason to change. If your ParkingLotService calculates fares, sends notifications, and manages spots, it needs to be split.
The test: describe what the class does in one sentence without the word "and." If you need "and," you need two classes.
Comments done right
Most interview code has too many comments, and they say the wrong thing. Comments explain why, not what. The code already tells you what it does.
Bad comments (remove these)
// β Restates the code
int count = 0; // initialize count to zero
count++; // increment count
// β Commented-out code (use version control)
// public void oldMethod() { ... }
Good comments (keep these)
// β
Explains a business rule
// Cap at 24 hours: billing contract charges flat daily rate beyond one day.
if (durationHours > MAX_PARKING_HOURS) {
return FLAT_DAILY_RATE;
}
// β
Explains a non-obvious technical decision
// ConcurrentHashMap: multiple kiosk threads assign spots simultaneously.
private final Map<String, ParkingSpot> spotMap = new ConcurrentHashMap<>();
The over-commenting trap
Candidates sometimes add comments to look thorough. It backfires. Every unnecessary comment is noise the interviewer has to skip. Write self-documenting code with good names and reserve comments for genuine surprises.
The best code needs few comments because the names and structure make intent obvious. No comment is almost always better than a bad comment.
Error handling
How you handle errors tells the interviewer how you think about edge cases. "What happens when the parking lot is full?" should not crash the program or return null silently.
Exceptions over return codes
Java has a rich exception system. Use it. Do not return -1 or null to signal failure.
// β Caller must remember to check for null
public ParkingSpot findSpot(VehicleType type) {
for (ParkingSpot spot : spots) {
if (!spot.isOccupied() && spot.fits(type)) return spot;
}
return null;
}
// β
Failure is impossible to ignore
public ParkingSpot findSpot(VehicleType type) {
return spots.stream()
.filter(s -> !s.isOccupied() && s.fits(type))
.findFirst()
.orElseThrow(() -> new NoAvailableSpotException(type));
}
Fail fast with custom exceptions
Validate inputs at the boundary. Do not let bad data propagate three layers deep before a NullPointerException surfaces.
// β
Validate at the entry point
public ParkingTicket parkVehicle(Vehicle vehicle) {
Objects.requireNonNull(vehicle, "Vehicle must not be null");
Objects.requireNonNull(vehicle.getLicensePlate(), "License plate required");
}
// β
Domain-specific exception: instantly understandable
public class NoAvailableSpotException extends RuntimeException {
private final VehicleType vehicleType;
public NoAvailableSpotException(VehicleType vehicleType) {
super("No available spot for vehicle type: " + vehicleType);
this.vehicleType = vehicleType;
}
}
One or two custom exceptions per LLD problem is plenty. Do not build an exception hierarchy for an interview.
Implementation: before and after
This is the full picture. A messy order service refactored into clean code. Study the before, understand what is wrong, then see how the after addresses every issue.
// β This class does too much: validation, pricing, payment, notification.
// Method names are vague. Variables are abbreviated. Magic numbers everywhere.
public class OrderProcessor {
private List<Object[]> orders = new ArrayList<>();
public int process(String cust, List<Object[]> items, String type) {
// validate
if (cust == null || cust.isEmpty()) return -1;
if (items == null || items.size() == 0) return -1;
// calc total
double t = 0;
for (Object[] item : items) {
double p = (double) item[0]; // price
int q = (int) item[1]; // qty
t += p * q;
}
// apply discount
if (type.equals("VIP")) {
t = t * 0.9; // 10% off
} else if (type.equals("EMPLOYEE")) {
t = t * 0.7; // 30% off
}
// tax
t = t * 1.18; // 18% tax
// save
Object[] order = new Object[]{cust, items, t, type};
orders.add(order);
// notify
System.out.println("Order placed for " + cust + " total: " + t);
return orders.size() - 1; // return index as "id"
}
}Look at what changed:
| Messy (before) | Clean (after) | Principle |
|---|---|---|
Object[] for everything | OrderItem record with types | Type safety, named fields |
String type with "VIP" | CustomerType enum | No magic strings |
double t | BigDecimal subtotal | Descriptive names, correct type for money |
| One 35-line method | Five focused classes | Single responsibility |
return -1 for errors | Exceptions with messages | Fail fast, clear contracts |
0.9 magic number | VIP_RATE constant | Named constants |
| Inline discount logic | DiscountStrategy interface | Open for extension |
Common mistakes
These are the patterns I see most often in interview code. Knowing them is half the battle.
1. Over-commenting
Delete every comment that restates the code. int count = 0; // initialize count to zero adds nothing. Comments are for the why, not the what.
2. Long methods
If you are scrolling past a method in the interview editor, it is too long. Extract until each method fits on a single screen.
3. Clever code
// β Takes 30 seconds to parse
return t == null ? -1 : t.equals("VIP") ? p * 0.9 : t.equals("EMP") ? p * 0.7 : p;
// β
Takes 2 seconds to understand
switch (customerType) {
case VIP -> subtotal.multiply(VIP_DISCOUNT);
case EMPLOYEE -> subtotal.multiply(EMPLOYEE_DISCOUNT);
default -> subtotal;
}
Clever code means the author is optimizing for their ego, not the reader.
4. Returning null
Every null return is a trap. Return an empty collection, throw an exception, or use Optional.
// β Caller must check for null
public ParkingSpot findSpot(VehicleType type) { /* ... might return null */ }
// β
Optional makes absence explicit
public Optional<ParkingSpot> findSpot(VehicleType type) {
return spots.stream()
.filter(s -> !s.isOccupied() && s.fits(type))
.findFirst();
}
Interview tip: fix as you go
If you spot messy code mid-interview, fix it. Saying "let me clean this up real quick" shows self-awareness. The interviewer would rather see a candidate who catches their own mistakes than one who never looks back.
The clean code checklist
Use this as a mental checklist in your next LLD interview. It takes ten seconds to scan before you start coding.
Quick recap
- Names are your first impression. Classes are nouns, methods are verbs, variables are descriptive, constants are UPPER_SNAKE_CASE.
- Small methods communicate seniority. One job per method. 5 to 15 lines. The orchestrator reads like a checklist.
- Comments explain decisions, not syntax. If a comment restates the code, delete it. If it explains a business rule or a non-obvious tradeoff, keep it.
- Fail fast with exceptions. Validate inputs at the boundary. Use custom exceptions for domain-specific failures. Never return null for "not found."
- Structure shows architectural thinking. Group classes by responsibility (model, service, strategy). Each class has one reason to change.
- Avoid cleverness. The best code is boring code. If the interviewer has to re-read a line, you wrote it wrong.
- Clean code is a habit, not a phase. Write it clean the first time. You will not have time to refactor in a 45-minute round.