Skip to main content

Guide: Amazon's Logical & Maintainable Coding Interview

How to prepare for Amazon's L&M round — the coding interview that grades you on clean code, extensibility, and design patterns instead of algorithms.

Who this is for

You’re preparing for an Amazon SDE II or SDE III loop. Your recruiter told you one round is “Coding Basics: Logical & Maintainable.” You’ve searched online and found conflicting advice — some say it’s LeetCode, others say it’s object-oriented design. This guide explains what it actually is, how it’s evaluated, and how to prepare in 2–3 weeks of focused practice.

The core insight

Among major tech companies, Amazon is one of the few that explicitly dedicates an interview round to evaluating code maintainability, extensibility, and software design skills — separate from algorithmic problem-solving. Other companies (Google, Stripe, Airbnb) evaluate code quality within their coding rounds, but Amazon carves it out as a distinct competency with its own rubric. This means you need to prepare for it differently.

The interviewer adds requirements mid-interview specifically to test whether your architecture bends or breaks. If adding a feature requires rewriting your solution, you fail. If it requires adding a class and plugging it into an existing interface, you pass.

This round exists because Amazon operates at a scale where code maintenance cost dominates — more engineering hours are spent reading, extending, and debugging code than writing it fresh. The L&M interview simulates exactly that: can you build something a stranger could extend tomorrow?

How this round works

Duration: ~35 minutes of coding after behavioral questions (total interview is 55–60 minutes).

Format: You write real, syntactically correct code in a shared editor. No pseudocode. The interviewer presents a base problem, you implement it, then they add 2–3 follow-up requirements that force your design to stretch.

The progression pattern:

  1. Interviewer states a simple problem (e.g., “find files by name in a directory”)
  2. You clarify requirements, sketch classes, confirm approach
  3. You implement the base case
  4. Interviewer adds a twist: “now also find by size,” “now combine criteria with AND/OR”
  5. You extend — ideally without touching your original code

This incremental-requirements pattern is the signature of L&M. It’s designed so that candidates who hardcoded the first case must rewrite, while candidates who abstracted correctly just add a new class.

What’s evaluated (the six criteria)

Amazon interviewers score across these criteria. They are listed in priority order — correctness is the foundation, everything else builds on top:

CriterionWhat “strong” looks likeWhat “concern” looks like
CorrectnessCode actually works end-to-end; edge cases handled; logic is soundCode doesn’t solve the problem; broken logic; unhandled nulls or boundaries
ReadabilityClear naming, logical structure, easy to follow without explanationInterviewer can’t follow the code; poor names, tangled flow
Complexity managementSingle-responsibility methods, each testable in isolationGod methods doing 5 things; deeply nested conditionals (>3 levels)
ExtensibilityNew requirements slot in cleanly; candidate explains how future cases would also fitAdding a requirement means rewriting; bolting on hacks with if/else
Design qualityAbstractions and interfaces emerge naturally from the problem; can articulate whyRepetitive if/else chains; copy-paste with slight variations; forced patterns
Coherent solutionEnd-to-end working code that meets all stated requirementsDesign doesn’t actually solve the problem, or requires interviewer to fill gaps

Important: Beautiful architecture that doesn’t produce correct output is a fail. Correctness is table stakes — the other criteria differentiate within the pool of candidates whose code works.

Bonus signal (not required): Can you articulate where this component fits in a larger system? What API would external callers use?

The two question categories

L&M questions fall into two distinct types. Both test the same criteria but through different lenses.

Category 1: Complex logic with clean structure

Problems that have inherent logical complexity but don’t require advanced algorithms — just clear decomposition.

The pattern: The problem itself is solvable with basic programming constructs (loops, conditionals, maps), but the naive solution produces unreadable spaghetti. The test is whether you can decompose the complexity into small, named, reusable pieces.

Example problems:

  • Convert an integer to its English representation (“1234” → “One Thousand Two Hundred Thirty Four”)
  • Roman numeral ↔ integer conversion
  • Implement a calculator that parses and evaluates expressions (with operator precedence)
  • Serialize/deserialize a data structure with a clean, extensible format

What bar-raising looks like: Helper methods with descriptive names (getHundredsPlace, formatThousandsGroup), data-driven lookup tables instead of switch statements, clear separation between parsing and logic.

What concern looks like: A single 80-line function with nested ifs, magic numbers, and variables named temp2.

Category 2: Library/component design (the “design-and-build” type)

Problems that model a reusable library or service component with multiple behaviors, rules, and extension points. Amazon L&M leans heavily toward library/component design rather than domain-entity modeling.

The pattern: You design a small system from scratch — classes, interfaces, relationships — then implement key methods. The interviewer’s follow-up requirements test whether your abstractions were well-chosen.

Example problems:

ProblemTypical follow-ups
File search library (Unix find)Search by size, date, extension; combine criteria with AND/OR; recursive flag; handle symlinks
Vending machineMultiple payment types; inventory tracking; change calculation; admin refill mode
Rate limiterMultiple algorithms (token bucket, sliding window); per-user vs global; distributed mode
Logging frameworkMultiple output targets; log levels; formatting; async writes
Set/cache with TTLThread safety; background purging; dependency injection for time
Pizza/order pricingFull orders (pizza + drinks); deals and promotions; per-store pricing
Amazon lockerPackage size matching; expiration; notification system

What bar-raising looks like: Clean interface boundaries, Strategy/Specification pattern for interchangeable behaviors, new requirements satisfied by adding classes (not modifying existing ones), working client code that demonstrates usage.

What concern looks like: One class doing everything, public fields everywhere, adding requirements means adding if (type == "new_thing") in six places.

The design patterns that actually matter

You don’t need to memorize the Gang of Four catalog. Six patterns cover 90% of L&M questions:

Specification / Strategy (most important)

Encapsulate a business rule or matching criterion as an object behind an interface.

When it appears: Any time the problem has interchangeable “criteria,” “rules,” “filters,” or “policies.” This is the single most important pattern for L&M — the File Search question (Amazon’s most classic L&M problem) is essentially a Specification pattern exercise.

interface FileFilter {
    boolean matches(File file);
}

class SizeFilter implements FileFilter {
    private final long minBytes;
    public boolean matches(File file) {
        return file.size() >= minBytes;
    }
}

class ExtensionFilter implements FileFilter {
    private final String ext;
    public boolean matches(File file) {
        return file.name().endsWith("." + ext);
    }
}

The power: each rule is independently testable, composable, and new rules require zero changes to existing code.

Composite (second most important)

Combine multiple specifications/strategies into compound expressions using the same interface.

When it appears: “Now find files that match criteria A AND criteria B.” “Apply deal X OR deal Y, whichever gives a better price.” This is the natural follow-up to Specification — interviewers use it to test whether your abstraction was right.

class AndFilter implements FileFilter {
    private final List<FileFilter> filters;
    public boolean matches(File file) {
        return filters.stream().allMatch(f -> f.matches(file));
    }
}

class OrFilter implements FileFilter {
    private final List<FileFilter> filters;
    public boolean matches(File file) {
        return filters.stream().anyMatch(f -> f.matches(file));
    }
}

Factory

Create objects without specifying the exact class. Useful when the interviewer’s follow-up introduces new types.

When it appears: Vending machine product creation, rate limiter algorithm selection, locker size assignment.

State

Model an object whose behavior changes based on internal state. Eliminates complex conditional logic.

When it appears: Vending machine (idle → selecting → paying → dispensing), order lifecycle, elevator direction.

Decorator

Wrap an object to add behavior without changing its interface.

When it appears: Pizza toppings adding cost, deals modifying order price, logging/caching wrapping a service.

Dependency Injection

Pass collaborators in through the constructor rather than creating them internally. This is less a “design pattern” and more a fundamental technique — but it’s tested so frequently in L&M follow-ups that it deserves its own section.

When it appears as a follow-up:

// BAD: untestable, coupled to system clock
class TTLCache {
    boolean isExpired(Entry e) {
        return System.currentTimeMillis() > e.expiresAt;
    }
}

// GOOD: testable, injectable
class TTLCache {
    private final Clock clock;
    TTLCache(Clock clock) { this.clock = clock; }

    boolean isExpired(Entry e) {
        return clock.now() > e.expiresAt;
    }
}

Common DI targets in L&M questions:

  • Time — inject Clock instead of calling System.currentTimeMillis() (TTL cache, rate limiter)
  • Storage — inject Storage interface instead of hardcoding file I/O (logging framework, file search)
  • Configuration — inject config rather than reading from environment (rate limiter thresholds)

Why interviewers love this: It simultaneously tests testability awareness, interface design, and separation of concerns. For SDE III candidates, proposing DI unprompted is strong signal.

When NOT to use patterns

If the problem doesn’t call for it, don’t force it. A simple problem with a forced Builder pattern looks worse than clean procedural code. The interviewer is testing judgment, not pattern-spotting. If you can articulate why a pattern fits — “I’m using Specification here because the matching criteria will expand, and I don’t want callers to change when we add new filters” — that’s strong signal.

Concurrency follow-ups

Thread safety is one of the most common follow-up questions in L&M interviews, especially for SDE III candidates. Even if the original problem is single-threaded, expect the interviewer to ask:

Common questions:

  • “Is this thread-safe?”
  • “What state is shared between threads?”
  • “What happens if multiple threads call this concurrently?”
  • “Would you use locks here? What kind?”
  • “Can you make reads concurrent while writes are exclusive?”
  • “How would you test for race conditions?”

Where it shows up by problem:

ProblemConcurrency angle
TTL cacheConcurrent add / contains / purge — shared data structure access
Rate limiterConcurrent request counting — atomic operations vs locks
File searchParallel directory traversal — thread pool + result aggregation
Logging frameworkConcurrent writes from multiple threads — buffer management
Vending machineConcurrent user sessions — state machine race conditions

What “meets the bar” looks like:

  • Identify shared mutable state
  • Propose synchronized or ReentrantReadWriteLock with correct granularity
  • Mention concurrent data structures as an alternative (ConcurrentHashMap, AtomicInteger)

What “raises the bar” looks like:

  • Propose non-blocking approaches where appropriate
  • Identify contention risks (“If purging blocks add, we get the same latency problem we’re trying to avoid”)
  • Discuss thread pool lifecycle and resource management
  • Question whether the concurrency model is per-instance or shared

For SDE III candidates, concurrency awareness is often evaluated even when the original problem statement doesn’t mention threads. Be ready to volunteer: “Should I consider thread safety for this?”

End-to-end walkthrough: File Search Library

This is the most frequently reported Amazon L&M question. Walking through it demonstrates exactly what “extensible design” means in practice.

Step 1: Base requirement

“Implement a library that finds all files under a directory whose name contains a given string.”

First instinct (what most candidates write):

class FileFinder {
    List<File> find(Directory dir, String nameContains) {
        List<File> results = new ArrayList<>();
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                results.addAll(find((Directory) f, nameContains));
            } else if (f.name().contains(nameContains)) {
                results.add(f);
            }
        }
        return results;
    }
}

This works. But it hardcodes the matching criterion into the traversal. Watch what happens next.

Step 2: Follow-up — “Now also find files larger than 5 MB”

The naive extension (concern signal):

// Adding another parameter — this doesn't scale
List<File> find(Directory dir, String nameContains, Long minSize) { ... }

Every new criterion adds a parameter. By the fourth criterion you have 8 parameters, most of them null.

The refactored approach (strength signal):

interface FileFilter {
    boolean matches(File file);
}

class NameFilter implements FileFilter {
    private final String substring;
    NameFilter(String substring) { this.substring = substring; }
    public boolean matches(File file) {
        return file.name().contains(substring);
    }
}

class SizeFilter implements FileFilter {
    private final long minBytes;
    SizeFilter(long minBytes) { this.minBytes = minBytes; }
    public boolean matches(File file) {
        return file.size() >= minBytes;
    }
}

class FileFinder {
    List<File> find(Directory dir, FileFilter filter) {
        List<File> results = new ArrayList<>();
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                results.addAll(find((Directory) f, filter));
            } else if (filter.matches(f)) {
                results.add(f);
            }
        }
        return results;
    }
}

Now the traversal logic never changes again. New criteria = new FileFilter implementation. Zero modification to FileFinder.

Step 3: Follow-up — “Find files that match BOTH name AND size”

With the right abstraction, this is trivial:

class AndFilter implements FileFilter {
    private final List<FileFilter> filters;
    AndFilter(FileFilter... filters) { this.filters = List.of(filters); }
    public boolean matches(File file) {
        return filters.stream().allMatch(f -> f.matches(file));
    }
}

// Usage:
finder.find(root, new AndFilter(
    new NameFilter(".xml"),
    new SizeFilter(5_000_000)
));

Step 4: Follow-up — “Also support OR”

class OrFilter implements FileFilter {
    private final List<FileFilter> filters;
    OrFilter(FileFilter... filters) { this.filters = List.of(filters); }
    public boolean matches(File file) {
        return filters.stream().anyMatch(f -> f.matches(file));
    }
}

One new class. No existing code changed.

This tests edge-case thinking:

  • Symlinks can create cycles → need a visited set
  • Should we follow symlinks or not? → make it configurable (another filter, or a traversal option)

The final class hierarchy

FileFilter (interface)
├── NameFilter
├── SizeFilter
├── ExtensionFilter
├── DateFilter
├── AndFilter (composite)
└── OrFilter (composite)

FileFinder
└── find(Directory, FileFilter) — recursive traversal, delegates matching

Why this impresses interviewers

  • Step 2 shows you can recognize the abstraction boundary after one concrete case
  • Steps 3–4 show your abstraction was right — composites plug in without touching existing code
  • Step 5 shows you think about production edge cases
  • The entire design follows the Open/Closed Principle without ever naming it

SDE II typically reaches Step 3 with some hints. SDE III proposes the FileFilter interface before writing the first implementation and volunteers the composite idea unprompted.

Practice problems: OO design (by frequency)

Ordered by how frequently these are reported in Amazon L&M interviews. If your time is limited, work top-down.

Very common

ProblemCore patternKey follow-ups
File search librarySpecification + CompositeSee walkthrough above
TTL cache / expiring setInterface extraction + DIThread safety; background purging; inject clock for testing
Vending machineState machinePayment types; inventory; change; admin mode
Logging frameworkStrategy + CompositeOutput targets (file/console/remote); levels; formatting; async
Rate limiterStrategyToken bucket vs sliding window; per-user vs global; distributed

Common

ProblemCore patternKey follow-ups
Parking lotFactory + StrategyVehicle types; multi-level; pricing tiers; nearest spot
Amazon LockerStrategy + FactorySize matching; expiration; notifications
Pizza/order pricingDecorator + StrategyCombos; deals/promotions; per-store pricing
Elevator systemState + StrategyDirection optimization; multiple elevators; priority

Occasionally reported

ProblemWhat it tests
Splitwise / expense sharingEntity relationships; balance calculation; settle-up algorithm
Chess gamePolymorphic piece hierarchy; movement validation; board state
Text editor with undo/redoCommand pattern; state snapshots; clean operation interface
Meeting schedulerConflict detection; room allocation; recurring events
Notification serviceChannel abstraction (email/SMS/push); routing; templates
Key-value storeGET/SET/DELETE with TTL; eviction policies; layered abstractions
JSON parserRecursive descent; tokenizer → parser → value separation

How to practice these

For each problem, spend 40 minutes:

  1. 5 min — sketch classes and interfaces (on paper or whiteboard, not IDE)
  2. 25 min — implement in your interview language
  3. 10 min — invent a follow-up requirement and extend

Grade yourself: did the extension require modifying existing classes, or just adding new ones?

Practice problems: LeetCode

These LeetCode problems train the same decomposition muscle. The goal isn’t to solve more problems — it’s to practice writing clean code under time pressure.

Must-do (5 problems that cover both L&M categories)

#ProblemWhy it’s essential
273Integer to English WordsForces recursive decomposition by magnitude; the canonical “clean helpers” problem
355Design TwitterMulti-entity OO design; clean feed generation from components
146LRU CacheDual data structure coordination; clean get/put/eviction boundary
68Text JustificationMulti-step formatting; forces helper extraction for padding and line-building
227Basic Calculator IITokenizer + evaluator separation; operator precedence via clean stack logic

Solve all five with one rule: no function longer than 10 lines. This forces the decomposition habit.

Optional (grouped by what they train)

Parser / expression decomposition:

#ProblemWhat it trains
224Basic CalculatorNested parentheses; recursive subexpression handling
394Decode StringNested bracket parsing; clean recursive decoder
726Number of AtomsMultiplier propagation through recursive descent
12Integer to RomanData-driven value table instead of cascading if/else
13Roman to IntegerClean lookup with subtraction rule handling

Simulation / state management:

#ProblemWhat it trains
54Spiral MatrixDirection state machine with boundary tracking
289Game of LifeSeparating “count neighbors” from “apply rules”
735Asteroid CollisionStack simulation; clean case dispatch
48Rotate ImageLayer-by-layer with swap helpers; isolated index math

Design-a-class:

#ProblemWhat it trains
588Design In-Memory File SystemPath parsing + tree CRUD; real-world modeling
1396Design Underground SystemState tracking across check-in/check-out
1472Design Browser HistoryForward/back state machine; cursor management
2296Design a Text EditorMulti-operation state with shared cursor logic
348Design Tic-Tac-ToeO(1) win-check via counters

How to use this list

Start with the 5 must-do problems. If you can solve each cleanly in 30 minutes with well-named helpers, you have the right muscle. Only move to the optional list if you want more reps on a specific weakness (e.g., you struggle with parser decomposition).

The point isn’t volume — it’s refactoring quality. After each solve, ask:

  • Could someone read this without comments and understand the flow?
  • Is each helper reusable, or does it embed assumptions about its one caller?
  • If I added a variant (new format, new rule, new entity type), where would it go?

How to prepare (the 3-week approach)

Week 1: Build the muscle

Pick 5 OO design problems from the “very common” list and implement each from scratch in your interview language. Spend 40 minutes per problem (simulating interview time pressure). After each:

  • Review: could a stranger extend this without reading the full implementation?
  • Test: add a requirement you didn’t plan for. How much code changes?
  • Refactor: extract any method longer than 10 lines

Week 2: Practice the incremental pattern

For each problem, define 3 follow-up requirements before you start coding. Then implement the base case, pause, and extend. Grade yourself:

  • Did the follow-up require rewriting, or just adding?
  • Are your interface boundaries in the right place?
  • Could you explain your design choice in one sentence?

Key drill: After implementing, ask yourself: “If I hand this to another engineer and say ‘add support for X,’ could they do it without reading my implementation — just the interfaces?”

Week 3: Simulate full rounds

Run 3–4 timed mock interviews (35 minutes). Use problems you haven’t seen. Practice:

  • Spending the first 5 minutes on requirements and class sketching (not coding)
  • Talking through decisions aloud (“I’m making this an interface because I expect more types later”)
  • Handling the curveball gracefully — when a new requirement breaks your design, acknowledge it and refactor visibly

Resources

  • Awesome Low-Level Design — the most comprehensive open collection of OO design problems with solutions. Work through the Amazon-tagged ones first.
  • Refactoring Guru: Design Patterns — quick visual reference for pattern structure and when to use each. Covers Specification, Strategy, Composite, State, Decorator with real examples.
  • Head First Design Patterns (Freeman & Robson) — the clearest pattern reference if you need to brush up. Chapters on Strategy and Composite are directly relevant.

During the interview: the 35-minute playbook

Minutes 0–5: Requirements and design. Ask clarifying questions. Sketch 3–5 classes/interfaces on screen. Name the key methods. Confirm with the interviewer: “Does this structure make sense before I start coding?”

Minutes 5–20: Implement the base case. Start with interfaces, then implementations. Name everything clearly. Keep methods short. Talk as you code — explain why you’re creating an abstraction, not just what it does.

Minutes 20–30: Handle follow-ups. When the interviewer adds requirements, pause. Say: “Let me think about where this fits in my current design.” Then either:

  • Add a new class implementing an existing interface (strong signal), or
  • Acknowledge the design needs adjustment, refactor, then extend (still fine — shows adaptability)

Minutes 30–35: Wrap-up. If time allows, address one or more of:

  • “Here’s how I’d test this” — name 3–4 test cases including edge cases
  • “Here’s how I’d diagnose issues at runtime” — logging, metrics, tracing
  • “Here’s how this handles scale” — identify the bottleneck if called 10K times/second
  • “Here’s what I’d inject for testability” — clock, storage, config

Common failure modes

The God Class. Everything lives in one class with 15 methods. When the follow-up arrives, there’s nowhere clean to put it. The fix: if you catch yourself adding a fourth responsibility to a class, stop and split.

Premature coding. Jumping into implementation before sketching the class relationships. The first 5 minutes of design save 15 minutes of rewriting. Force yourself to draw boxes before writing code.

Pattern museum. Using Singleton + Factory + Builder + Observer for a vending machine. The interviewer reads this as “memorized patterns, doesn’t know when they apply.” Use only what the problem demands.

The rewrite panic. When a follow-up doesn’t fit your design, some candidates freeze or start over. Instead: acknowledge the gap, identify the minimal refactor, execute it. Interviewers want to see you adapt — that’s the whole point of the round.

Silent coding. The interviewer can’t score what they can’t observe. If you’re not talking, they’re guessing. Narrate your design decisions, especially the “why.” You can be brief — “I’m extracting this because the follow-up will probably need another variant” — but don’t go silent.

Pseudo-code trap. Amazon explicitly requires syntactically correct code. Practice in your actual interview language until standard library calls are automatic. If you forget a method name, make one up with clear semantics and move on — but the structure must be real code.

Correct-but-fragile. Code works for the stated requirements but can’t absorb any change. This is the inverse of the first failure mode — correctness without extensibility. Both matter; neither alone is sufficient.

What separates levels

SDE II:

  • Implements the base case with clean structure without prompting
  • Recognizes the need for abstraction after implementing one concrete case (sees the pattern after the fact)
  • Extends the design for follow-ups with minimal hints
  • Can discuss testing approach when asked
  • Handles basic concurrency when prompted

SDE III:

  • Designs the abstraction before implementing the first case (anticipates extensibility)
  • Identifies composite/combinator needs without prompting (“Should I also handle combining these filters?”)
  • Proposes dependency injection for testability unprompted
  • Handles edge cases proactively (symlinks in file search, thread safety in TTL cache)
  • Can articulate broader architectural context (“This library would sit behind an API gateway; here’s the interface external callers would use”)
  • Volunteers concurrency considerations even when the problem is stated as single-threaded

The gap isn’t knowledge — it’s timing. SDE II sees the abstraction after the second case. SDE III sees it before the first.

The code review variant

Some L&M interviews include a code review segment: the interviewer shows you buggy or poorly structured code and asks for feedback. This tests the same criteria from a different angle.

What to look for (in priority order):

  1. Correctness bugs — race conditions, off-by-one, null handling, exception misuse
  2. Interface problems — should this method be public? Is the caller forced to know implementation details?
  3. Structural issues — methods doing too much, inconsistent abstraction levels, naming that misleads
  4. Testability — can this be unit tested? What would you inject?
  5. Style — formatting, naming conventions, magic numbers

How to deliver feedback:

  • Start with the most impactful issue, not the first line you notice
  • Explain why it’s a problem, not just that it is (“this synchronized block doesn’t protect the contains method, so concurrent callers can get stale data”)
  • Propose a fix, not just a complaint
  • For style issues, advocate for automated tooling rather than manual policing

What to do next

  • Work through the file search library walkthrough above end-to-end in your interview language. Time yourself — can you reach Step 4 in 25 minutes?
  • Solve LC 273 (Integer to English Words) with the “no function longer than 10 lines” rule.
  • Practice the TTL cache problem with thread safety as the follow-up — this combines OO design with concurrency.
  • Review your interview language’s concurrency primitives (synchronized, ReentrantLock, ConcurrentHashMap in Java; threading.Lock, queue.Queue in Python).
  • Browse awesome-low-level-design for worked solutions to the OO design problems above.