Beginner

Tic-Tac-Toe

Here is the complete end-to-end Low Level Design (LLD) solution for Tic Tac Toe implemented in Python.

This implementation follows Object-Oriented principles, keeping the design modular, extensible, and easy to maintain.

1. The Setup (Enums and Pieces)

First, we define the types of pieces and the base classes. This allows us to easily add new symbols (like '$' or '&') later without breaking the code.

Python

from enum import Enum

# Enum for piece type (Scalable for future piece types)
class PieceType(Enum):
    X = 1
    O = 2

# Base Class for a Playing Piece
class PlayingPiece:
    def __init__(self, piece_type: PieceType):
        self.piece_type = piece_type

# Concrete Class for X
class PlayingPieceX(PlayingPiece):
    def __init__(self):
        super().__init__(PieceType.X)

# Concrete Class for O
class PlayingPieceO(PlayingPiece):
    def __init__(self):
        super().__init__(PieceType.O)

2. The Board

The Board class manages the grid state. It handles printing the board, checking for free cells, and placing pieces.

Python


3. The Player

A simple class to bind a name to a specific piece.

Python


4. The Game Orchestrator (Main Logic)

The TicTacToeGame class initializes the game and runs the main loop. It uses a deque (double-ended queue) to manage player turns efficiently.

Python


5. Running the Game

To run the game, simply instantiate the class and call start_game.

Python

Key Design Highlights for Interviews:

  1. Deque for Players: Using collections.deque allows for rotation of players. This makes it very easy to support 3 or 4 players in the future (just add them to the queue).

  2. Validation: The add_piece method returns a boolean, allowing the Game class to handle invalid moves gracefully (by asking the user again).

  3. Extensibility: If the interviewer asks "Can we add a Triangle piece?", you just create class PlayingPieceTriangle and add it to the init logic. No core game logic needs changing.

Snake & Ladder Game

Here is a complete Low Level Design (LLD) solution for the Snake and Ladder Game.


1. Requirements Clarification

  • Board: Standard 10x10 grid (100 cells).

  • Dice: A standard 6-sided dice.

  • Players: Multiple players (2 or more).

  • Snakes & Ladders: Randomly placed. Snakes take you down (start > end), Ladders take you up (start < end).

  • Movement: Players start at position 0. They roll the dice and move forward.

  • Winning Condition: The first player to reach or exceed position 100 wins.


2. Core Entities (Objects)

  1. Jump: Represents a connection between two cells. Used for both Snakes and Ladders.

    • Properties: start, end.

  2. Cell: Represents a square on the board. Can hold a Jump.

  3. Board: Holds the grid of Cells. Responsible for knowing where the snakes and ladders are.

  4. Dice: Responsible for generating a random number (1-6).

  5. Player: Holds ID and current position.

  6. Game: Orchestrator. Manages the flow (rolling dice, checking jumps, turn rotation).


3. Class Diagram & Relationships

Here is the UML diagram representing the design.

Code snippet


4. Python Implementation

A. The Jump & Cell Classes

Instead of creating separate classes for Snake and Ladder, we use a generic Jump class.

  • If start < end Ladder.

  • If start > end Snake.

Python

B. The Board

The board initializes the grid and randomly places snakes and ladders.

Python

C. The Dice

Supports rolling standard dice. Can be extended to support multiple dice.

Python

D. The Player

Python

E. The Game Orchestrator

This manages the core loop: Pop player Roll Dice Calculate Move Check Win Push player back.

Python


5. Design Considerations for Interview

1. Why use a Jump class instead of Snake/Ladder subclasses?

This simplifies the Board logic. The board only needs to know "Is there a jump here?". It doesn't need to know the physics of snakes vs ladders. The logic is purely current_pos = jump.end.

2. Design Pattern: Strategy Pattern (Dice)

If the interviewer asks: "What if we have a rigged dice? Or a 20-sided dice?"

You can use the Strategy Pattern for the Dice.

  • Interface: DiceStrategy (roll())

  • Concrete: NormalDice, LoadedDice, DoubleDice.

    The Game class would just call dice.roll() regardless of the implementation.

3. Concurrency (Advanced)

If multiple games run on a server:

  • Make the Game class a unique session instance.

  • The Board can be a Singleton if the snakes/ladders are static (same for everyone). If every game has random snakes, the Board must be instantiated per game.

4. Code Extensibility (Adding "Skip Turn" or "Extra Turn")

We can extend Cell to have a CellEffect.

  • CellEffect could be an Enum or Interface: JUMP, EXTRA_TURN, SKIP_TURN.

  • The Game loop would check the effect at the new_pos and adjust the players deque accordingly (e.g., adding the player to the front again for "Extra Turn").

Tetris Game

An end-to-end Low Level Design (LLD) solution for the Tetris Game.


1. Requirements Clarification

  • Board: Standard grid (usually 10x20).

  • Pieces (Tetrominoes): 7 standard shapes (I, O, T, S, Z, J, L), each consisting of 4 blocks.

  • Movement: Pieces fall automatically. Players can move them Left, Right, Down (soft drop), or Rotate.

  • Collision: Pieces stop when they hit the bottom or another piece.

  • Line Clearing: When a row is full, it is cleared, and blocks above move down.

  • Game Over: If a new piece cannot be placed at the top, the game ends.


2. Core Entities (Objects)

  1. Block (Cell): The smallest unit. Has a state (occupied/empty) and color.

  2. Point: A helper class for coordinates (row, col).

  3. Tetromino (Piece): The falling shape. It has a list of Points representing its shape relative to a center or pivot.

  4. Board: The grid. Stores the state of "landed" blocks. Checks collisions and clears lines.

  5. Game: The controller. Handles the game loop, input, and score.


3. Class Diagram

Code snippet


4. Python Implementation

A. Helper Class (Point)

Used to manage coordinates easily.

Python

B. The Tetromino (Piece) Base Class & Factory

This manages the shape and rotation logic.

  • Rotation Logic: Standard matrix rotation. .

Python

C. The Board

Handles the grid state, collision detection, and line clearing.

Python

D. The Game Orchestrator

This manages the input and game loop (tick).

Python


5. Key Design Decisions (Interview Talking Points)

  1. Tetromino Factory:

    • Why: Decouples the Game class from specific shapes. If you want to add a "Bomb" piece or a "Single Block" piece later, you only modify the Factory, not the Game logic.

  2. Board as the source of truth:

    • Why: The Board knows boundaries and existing blocks. The Piece shouldn't check its own collision; the Board should check if a Piece fits.

  3. Rotation Logic:

    • Detail: Implementing rotation can be tricky. Using relative coordinates (Points relative to a center 0,0) makes the math very clean compared to rotating absolute indices.

  4. Game Loop vs Input:

    • Design: I separated handle_input (user action) from update (automatic gravity). This mimics real game engine architecture (Input Loop vs Update Loop).

Minesweeper

Here is the complete Low Level Design (LLD) solution for Minesweeper.


1. Requirements Clarification

  • Board: Grid of size .

  • Components:

    • Mines: Hidden randomly.

    • Numbers: Represents the count of mines in the 8 neighboring cells.

    • Empty: No neighboring mines (Value 0).

  • Actions:

    • Reveal (Click): Shows content.

      • If Mine Game Over (Loss).

      • If Number Reveal just that cell.

      • If Empty Auto-reveal neighbors recursively (Flood Fill).

    • Flag: Mark a cell as a potential mine.

  • Win Condition: All non-mine cells are revealed.


2. Core Entities

  1. Cell: The atomic unit. Needs to know if it's a mine, its adjacent mine count, and its current state (Hidden, Revealed, Flagged).

  2. Board: The grid. Handles mine placement, adjacency calculations, and the "Flood Fill" algorithm.

  3. Game: The controller. Manages input parsing, game status (Won/Lost/Playing), and time tracking.


3. Class Diagram & Relationships

Code snippet


4. Python Implementation

A. The Cell Class

We separate the data (mine/number) from the state (revealed/flagged).

Python

B. The Board Class (The Heavy Lifter)

This class handles the core complexity: Mine Placement and Flood Fill.

Python

C. The Game Orchestrator

Handles the winning logic and user input.

Python


5. Key Interview Points (The "Why")

1. Algorithm: Flood Fill (BFS vs DFS)

  • Question: How do you handle revealing an empty area?

  • Answer: I used BFS (deque) in the _flood_fill method.

    • Why BFS? It reveals the area layer-by-layer, which feels more natural in UI rendering, though DFS works fine too.

    • Logic: If I reveal a cell with 0, I add all its neighbors to the queue. If I reveal a cell with 1-8, I stop there (it's the border of the empty region).

2. Initialization Strategy (Advanced)

  • Question: What if the user clicks a mine on the very first turn? That's bad UX.

  • Optimization: In a production system, _place_mines() should be called after the first click.

    • Capture user's first (r, c).

    • Place mines randomly, ensuring (r, c) and its immediate neighbors are skipped.

    • Calculate adjacency numbers.

    • Proceed with the reveal.

3. Separation of Cell State vs Configuration

  • I separated is_mine (Configuration) from is_revealed (State).

  • This prevents "Cheating" logic where the UI might accidentally peek at the mine status before the user reveals it. The UI should only render based on is_revealed.

Chess Game

An end-to-end Low Level Design (LLD) solution for the Chess Game.


1. Requirements Clarification

  • Board: Standard 8x8 grid.

  • Pieces: King, Queen, Bishop, Knight, Rook, Pawn (White & Black sets).

  • Movement Rules: Unique logic for each piece type.

  • Game Flow: Turn-based (White moves first).

  • Special Moves: Castling, En Passant, Promotion.

  • Win Condition: Checkmate.

  • End Condition: Stalemate, Draw, Resignation.


2. Core Entities

  1. Block (Spot): A coordinate that may contain a piece.

  2. Piece: Abstract class. Contains color and logic for can_move.

  3. Board: 8x8 grid of Blocks.

  4. Player: Human or Engine.

  5. Move: Represents a start and end block, plus piece moved.

  6. Game: Orchestrator. Validates turns, checks for checkmate/stalemate.


3. Class Diagram & Design

Code snippet


4. Python Implementation

A. The Piece Hierarchy

We use an abstract base class Piece and implement movement logic in subclasses.

Python

B. The Board and Block

The Board initializes the grid and places pieces.

Python

C. The Move and Game Logic

This is where the complex rules (turns, checkmate) reside.

Python

D. The Player

Simple wrapper for player identity.

Python


5. Key Complexity Handling (Interview "Gotchas")

1. "Check" and "Checkmate" Validation

This is the hardest part of Chess LLD.

  • The Problem: A piece might physically be able to move to a spot, but if that move exposes the King to capture, the move is illegal.

  • The Solution: You implement a is_move_valid() method in the Game class that:

    1. Temporarily executes the move.

    2. Scans the board to see if the opponent can capture the King.

    3. If yes, revert the move and return False.

2. Special Moves (Castling, En Passant)

These violate standard movement rules.

  • Design: Add flags to the King and Rook classes: has_moved.

  • Logic: Inside King.can_move(), check:

    • Has King moved?

    • Has target Rook moved?

    • Is path clear?

    • Is path under attack? (King cannot castle through check).

3. Pawn Promotion

  • Design: When a Pawn reaches the last rank ( or ), the Game class must pause and ask for user input (Queen, Rook, etc.), then replace the Pawn object with the new Piece object.

Alarm/Alert Service for AWS

An end-to-end Low Level Design (LLD) for an Alarm/Alert Service (similar to AWS CloudWatch Alarms).


1. Requirements Clarification

  • Input: The system receives metrics (e.g., CPU Usage, Latency) from resources.

  • Rules (Alarms): Users can define rules (e.g., If CPU > 80% for 3 consecutive data points).

  • State Management: Alarms have states: OK ALARM.

    • We only notify on state transitions (to prevent spamming every second the CPU is high).

  • Notifications: Support multiple channels (Email, SMS, Webhook, PagerDuty).

  • Scalability: Must handle high throughput of incoming metrics.


2. Core Entities

  1. Metric: A data point consisting of {MetricName, Value, Timestamp, Dimensions}.

  2. Condition (Rule): Logic to evaluate the metric (e.g., GreaterThan, LessThan).

  3. Alarm: The core object binding a Metric to a Condition. It maintains the current State.

  4. Action: What to do when the state changes (e.g., SendEmail).

  5. AlertService: The Orchestrator. Ingests metrics and routes them to the correct alarms.


3. Class Diagram & Relationships

We use the Strategy Pattern for notification channels and the State Pattern (simplified) for alarm status.

Code snippet


4. Python Implementation

A. The Basic Data Structures

Enums and Metric class to standardize inputs.

Python

B. Notification Strategy (Strategy Pattern)

This allows us to easily add "Slack" or "PagerDuty" later without changing the Alarm class.

Python

C. The Alarm Logic (State Management)

This is the heart of the system. It checks the rule and manages the OK -> ALARM transition.

Python

D. The Orchestrator (Service)

Simulates the ingestion of metrics.

Python


5. Key Design Decisions (Interview Talking Points)

1. Handling Flapping (State Oscillation)

  • Problem: If CPU hovers at 80% (79, 81, 79, 81), the user gets spammed with "Alarm", "Resolved", "Alarm".

  • Solution: In Alarm class, implement M out of N logic.

    • Change self.required_breaches to 3.

    • The state only transitions to ALARM if we see 3 bad metrics in a row.

2. Strategy Pattern for Notifications

  • Why? Allows us to follow the Open/Closed Principle. We can add WebhookNotification or SlackNotification without modifying the Alarm class code.

3. Metric Matching (Scalability)

  • In the python code, I iterated all alarms (for alarm in self.alarms).

  • Optimization: In a real system, use a HashMap or Inverted Index.

    • Map: MetricName -> List[AlarmID].

    • When CPU_Usage comes in, we directly fetch only the relevant alarms.

4. Push vs Pull

  • This Design (Push): Metrics are pushed into the evaluator. Best for real-time.

  • Alternative (Pull): The evaluator queries a database periodically ("Select avg(cpu) from DB"). Best for complex aggregations.

Logging Library like log4j

An end-to-end Low Level Design (LLD) for a Logging Framework similar to Log4j or Python's logging module.


1. Requirements Clarification

  • Log Levels: Support standard levels (DEBUG, INFO, ERROR, etc.) with priority order.

  • Multiple Sinks (Appenders): Support writing to Console, Files, or Databases.

  • Formatters (Layouts): Support different output formats (Simple Text, JSON, XML).

  • Configuration: Ability to set different logging levels and appenders for different loggers.

  • Singleton/Factory: Central management of Logger instances.

  • Thread Safety: Writing to shared resources (like a file) must be thread-safe.


2. Core Entities

  1. LogLevel: Enum defining severity (DEBUG < INFO < ERROR).

  2. LogMessage: Encapsulates the data (message, timestamp, thread ID).

  3. Layout (Formatter): Strategy interface for converting a LogMessage into a string.

  4. Appender (Sink): Strategy interface for outputting the formatted string (Console, File).

  5. Logger: The main component used by clients. Filters messages based on Level and delegates to Appenders.

  6. LoggerManager: A centralized factory/singleton to manage logger instances.


3. Class Diagram & Relationships

We rely heavily on the Strategy Pattern (for Layouts and Appenders) and the Factory Pattern (for Logger creation).

Code snippet


4. Python Implementation

A. Basic Setup (Enum & Message)

We define the priority levels.

Python

B. Layouts (Strategy Pattern)

This handles how the log looks.

Python

C. Appenders (Strategy Pattern)

This handles where the log goes. Note the thread locking in FileAppender.

Python

D. The Logger

This component filters messages based on severity and delegates to all attached appenders.

Python

E. Logger Manager (Singleton/Factory)

Ensures we don't create duplicate logger instances for the same name.

Python


5. Key Interview Points

1. Why use the Strategy Pattern?

We separate Formatting (Layout) and Output (Appender).

  • This allows combinatorial freedom. I can have Console + JSON, File + JSON, or Console + SimpleText without creating classes like JsonConsoleAppender.

2. Thread Safety

  • Problem: If two threads write to app.log at the exact same time, lines might get interleaved/garbled.

  • Solution: The FileAppender holds a threading.Lock. It acquires the lock before writing and releases it after.

3. Performance (Lazy Evaluation)

  • Question: What if layout.format() takes 1 second, but the log level is filtered out?

  • Answer: In the Logger.log() method, we check if level < self.level before creating the LogMessage or calling any formatters. This prevents wasting CPU on logs that nobody will see.

4. Extensibility

  • Question: How to add sending logs to Slack?

  • Answer: Create a new class SlackAppender(Appender). Implement append(). Add it to the logger. No existing code needs to change (Open/Closed Principle).

JSON Parser

Here is the complete Low Level Design (LLD) solution for a JSON Parser.

Designing a parser usually involves two distinct stages:

  1. Lexical Analysis (Tokenizer): Converts the raw string into a list of meaningful tokens (e.g., "{", "key", ":", 123, "}").

  2. Syntactic Analysis (Parser): Processes the tokens to build the final data structure (Object/Array). We will use a Recursive Descent Parser.


1. Requirements Clarification

  • Input: A valid JSON string.

  • Output: A native object (Dictionary for {}, List for [], etc.).

  • Supported Types:

    • Objects: { "key": value }

    • Arrays: [ value1, value2 ]

    • Strings: "hello"

    • Numbers: 123, 4.56

    • Booleans/Null: true, false, null

  • Constraints: Must handle nested structures (e.g., Object inside Array inside Object).


2. Core Architecture & Entities

  1. TokenType: Enum representing the type of token (L_BRACE, R_BRACE, STRING, NUMBER, etc.).

  2. Token: A data class holding the type and the raw value.

  3. Lexer (Tokenizer): Scans the input string character by character and produces a stream of Tokens.

  4. Parser: Consumes the stream of Tokens. It has specific methods for parsing Objects (parse_object) and Arrays (parse_array).


3. Class Diagram


4. Python Implementation

A. The Setup (Tokens)

We define the building blocks of the JSON language.

Python

B. The Lexer (Tokenizer)

This class handles the dirty work of reading characters. It skips whitespace and groups characters into tokens.

Python

C. The Parser (Recursive Descent)

This uses the tokens to build the structure. The core idea is parse_value determines the type, and delegates to parse_object or parse_array which recursively call parse_value.

Python


5. Key Interview Points

1. Why Recursive Descent?

Recursive Descent is chosen because the JSON structure is naturally recursive:

  • An Object contains Values.

  • A Value can be an Object.

    This mutual recursion (parse_object calls parse_value calls parse_object) makes the code mirror the grammar definitions almost 1:1, making it readable and easy to debug.

2. Handling Ambiguity

JSON is unambiguous (LL(1) grammar).

  • We only need to look at one token to know what to do next.

  • If we see {, we know it's an object. If we see [, we know it's an array. This makes the parser very fast as no backtracking is required.

3. Error Handling

Notice the _consume(expected_type) helper.

  • This enforces structure. If we are inside an object and see a Key, we must see a Colon next. If not, consume raises a clear error immediately.

4. Extensions

  • Streaming: For huge JSON files (gigabytes), you cannot load all tokens into memory. You would modify the Lexer to be a generator (yield tokens one by one) and the Parser to consume them lazily.

Would you like to explore "Design a Rate Limiter" (Token Bucket Algorithm) or "Design a URL Shortener" next?

File System

Here is the complete Low Level Design (LLD) solution for a File System.

This problem is the textbook example of the Composite Design Pattern, where individual objects (Files) and compositions of objects (Directories) are treated uniformly.


1. Requirements Clarification

  • Entities:

    • File: Has a name, content (binary/text), size, and metadata.

    • Directory: Has a name and can contain multiple Files or sub-Directories.

  • Operations:

    • Traversal: cd, ls (list files).

    • Manipulation: mkdir (create dir), touch (create file), rm (delete).

    • IO: write to file, read from file.

  • Properties:

    • Hierarchy: Must support nested structures ().

    • Metadata: Size, Creation Time, Last Updated Time.


2. Core Entities

  1. FileSystemEntry (Abstract Class): The common parent. Defines shared attributes (name, created_at) and abstract methods (get_size()).

  2. File (Leaf): Represents a concrete file. Implements get_size() based on content length.

  3. Directory (Composite): Represents a folder. Holds a list of children (List<FileSystemEntry>). Implements get_size() by recursively summing children's sizes.

  4. FileSystem: The orchestrator. Holds the reference to the Root directory and manages path resolution (converting /home/user/docs into actual objects).


3. Class Diagram & Relationships

We use the Composite Pattern: A Directory is an Entry, but it also contains Entry objects.


4. Python Implementation

A. The Base Component (Abstract Class)

This ensures both Files and Directories can be stored in the same list and accessed via the same interface.

Python

B. The Leaf (File)

Implements size based on data content.

Python

C. The Composite (Directory)

Implements size based on recursion. Holds a collection of children.

Python

D. The Orchestrator (File System)

Handles path parsing (string object).

Python


5. Key Interview Points

1. The Composite Pattern

  • Why? The interviewer is testing if you know how to treat a collection of objects (Directory) the same way as a single object (File).

  • Proof: Both inherit from FileSystemEntry. You can call get_size() on the root folder, and it cascades down gracefully without knowing whether the children are files or folders.

2. Path Resolution

  • The logic inside _resolve_path (splitting string by / and traversing nodes) is critical. It shows you understand tree traversal.

3. Circular Dependencies

  • Question: What if I create a symlink (shortcut) that points to a parent directory, creating an infinite loop for get_size()?

  • Answer: In a simple LLD, we assume a Tree structure (DAG). In a real OS (Graph structure), get_size() would need a Set<VisitedNode> to prevent infinite recursion.

4. Search Functionality

  • You can easily add a recursive search method to Directory:

    Python

Would you like to explore "Design a Cache System (LRU/LFU)" next?

2048

An end-to-end Low Level Design (LLD) solution for the 2048 Game.


1. Requirements Clarification

  • Board: A 4x4 grid.

  • Tiles: Numbered tiles (starting with 2 or 4).

  • Movement:

    • Players swipe Left, Right, Up, or Down.

    • All tiles slide as far as possible in that direction.

    • Merge Logic: If two tiles of the same number collide while moving, they merge into one tile with double the value (e.g., ).

    • A merge can only happen once per tile per move.

  • Random Spawn: After every valid move, a new tile (2 or 4) spawns in a random empty spot.

  • Game Over: No empty spots and no adjacent tiles with the same value (no moves possible).

  • Win Condition: A tile with value 2048 is created.


2. Core Entities

  1. Tile: Represents a single cell. Holds the value (int). Can check if it is empty.

  2. Board: The 4x4 grid. Handles the complex logic of Sliding and Merging.

  3. Game: The controller. Manages input, score, game status (Won/Lost), and the game loop.

  4. Direction (Enum): UP, DOWN, LEFT, RIGHT.


3. Class Diagram


4. Python Implementation

A. The Tile Class

Simple wrapper for the number.

Python

B. The Board (The Core Algorithm)

This is where the interview difficulty lies: implementing the Shift & Merge logic correctly.

Strategy:

Instead of writing 4 different functions for Up/Down/Left/Right, we can normalize the problem:

  1. Left/Right: Process rows.

  2. Up/Down: Transpose the grid (swap rows and cols), process as rows, then transpose back.

  3. Reverse: For Right/Down, reverse the row, process as Left, then reverse back.

This reduces the problem to writing one robust function: merge_left(row).

Python

C. The Game Controller

Python


5. Key Design Decisions (Interview Talking Points)

1. Matrix Manipulation Strategy

  • Problem: Writing 4 separate loops for Up/Down/Left/Right is prone to bugs and code duplication.

  • Solution: I used a "Canonical" approach.

    • I wrote one logic: slide_left.

    • To slide RIGHT: I reverse the row, slide left, and reverse back.

    • To slide UP: I transpose the matrix, slide left, and transpose back.

    • This is a cleaner, more mathematical approach that impresses interviewers.

2. Merge Logic (Greedy Approach)

  • Detail: When merging [2, 2, 2, 2], does it become [4, 4, 0, 0] or [8, 0, 0, 0]?

  • Rule: Standard 2048 rules say [4, 4]. Each tile can only merge once per turn.

  • Implementation: I used a skip flag in the loop. If index i merges with i+1, I append the merged result and force the loop to skip i+1.

3. Check Game Over

  • Edge Case: The board is full, but the game isn't over if adjacent tiles are equal (e.g., [2, 2]).

  • Logic: The is_game_over function checks:

    1. Are there any zeros? (If yes, continue).

    2. Are there any horizontal neighbors equal?

    3. Are there any vertical neighbors equal?

4. Separation of Concerns

  • Board vs Tile: The Tile is dumb; it just holds a number. The Board is smart; it knows grid geometry.

  • Board vs Game: The Board handles matrix math. The Game handles input parsing and the while loop.

Would you like to move on to "Design a Parking Lot" or "Design a Vending Machine"?

Last updated