Bird
Raised Fist0

Design an Online Tic-Tac-Toe Game - Board, Player, Win Detection, Replay

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
🏗️
Design an Online Tic-Tac-Toe Game - Board, Player, Win Detection, Replay
Design a concurrent online Tic-Tac-Toe game with player management, board state, win detection, and replay functionality.
mediumGame DesignFreq: medium
Prerequisites:OOP FundamentalsJava concurrency basicsDesign patterns (Strategy, Observer)
⚙️
Functional Requirements
  • Support two players per game session
  • Maintain board state with 3x3 grid
  • Validate moves to prevent overwriting
  • Detect win conditions (rows, columns, diagonals)
  • Allow replay of completed games
  • Notify players and spectators of game state changes
  • Handle concurrent move attempts gracefully
👤
Use Cases
  • Player joins a game
  • Player makes a move on the board
  • System detects a win or draw
  • Players can replay the game
  • Spectators watch ongoing games
🧑‍💻
Actors
  • Player
  • Game Server
  • Spectator
❓
Clarifying Questions to Ask
  • How many concurrent games and players should the system support?
  • Are moves submitted via synchronous or asynchronous calls?
  • Should the system handle network latency or disconnections?
  • Is there a pricing or monetization strategy for premium features?
  • What happens if both players attempt to move simultaneously?
  • Should the replay feature allow step-by-step navigation or auto-play?
  • Are spectators allowed to join mid-game or only before start?
Out of Scope
✗ User authentication and authorization
✗ Persistent storage of game history
✗ Chat or voice communication
✗ AI opponent implementation
🔢
Enumerations
enum PlayerType
HUMAN | BOT
Defines player categories to enable future AI integration and prevent string typos.
enum CellState
EMPTY | X | O
Represents board cell states explicitly, enabling exhaustive switch statements and avoiding magic strings.
enum GameStatus
IN_PROGRESS | DRAW | X_WON | O_WON
Tracks game lifecycle states to enforce valid transitions and notify observers.
📦
Player
3 fields
private final String playerIdprivate final PlayerType typeprivate final CellState symbol
Represents a game participant with unique ID, type, and assigned symbol (X or O).
public String getPlayerId()Returns the unique identifier of the player.
public PlayerType getType()Returns the type of the player (HUMAN or BOT).
public CellState getSymbol()Returns the symbol assigned to the player (X or O).
📦
Cell
1 fields
private volatile CellState state
Represents a single cell on the Tic-Tac-Toe board, holds its current state.
public synchronized CellState getState()Returns the current state of the cell.
public synchronized boolean setState(CellState newState)Atomically sets the cell state if it is EMPTY; returns true if successful.
📦
Board
1 fields
private final Cell[][] cells = new Cell[3][3]
Maintains the 3x3 grid of cells and provides methods to update and query board state.
public void initialize()Initializes all cells to EMPTY state.
public synchronized boolean makeMove(int row, int col, CellState symbol)Attempts to place the symbol at specified position atomically; returns false if cell occupied.
public synchronized CellState getCellState(int row, int col)Returns the state of the cell at given coordinates.
📦
Game
6 fields
private final Board boardprivate final Player playerXprivate final Player playerOprivate volatile Player currentPlayerprivate volatile GameStatus statusprivate final List<GameObserver> observers
Manages game lifecycle, player turns, win detection, and notifies observers of state changes.
public synchronized void start()Starts the game, sets initial player and status.
public synchronized boolean makeMove(Player player, int row, int col)Processes a player's move if valid and updates game status.
private synchronized GameStatus checkWin()Checks the board for a win or draw condition.
public synchronized void addObserver(GameObserver observer)Registers an observer to receive game state updates.
private synchronized void notifyObservers()Notifies all observers about the current game state.
public synchronized void reset()Resets the game to initial state for replay.
📦
GameObserver
0 fields
Interface for observers to receive game state updates.
public void onGameStateChanged(Game game)Called when the game state changes.
📦
GameManager
1 fields
private final Map<String, Game> activeGames = new ConcurrentHashMap<>()
Manages multiple concurrent games, player matchmaking, and game lifecycle.
public synchronized Game createGame(Player playerX, Player playerO)Creates and registers a new game with two players.
public Game getGame(String gameId)Retrieves a game by its unique identifier.
public synchronized void removeGame(String gameId)Removes a finished or abandoned game.
🔗
Relationships
Game◆──►Board[1 to 1]
compositionBoard lifecycle bound to Game lifecycle
Game──►Player[1 to 2]
associationPlayers exist independently of Game
Board◆──►Cell[1 to 9]
compositionCells lifecycle bound to Board lifecycle
Game◇──►GameObserver[1 to many]
aggregationObservers can exist independently of Game
🗄️
Games
PK: gameId
gameId STRINGplayerXId STRINGplayerOId STRINGstatus STRINGboardState STRINGcurrentPlayerId STRINGcreationTimestamp TIMESTAMP
Stores game metadata and current state.
🗄️
Moves
PK: gameId
gameId STRINGmoveNumber INTplayerId STRINGrow INTcol INTsymbol STRINGtimestamp TIMESTAMP
Stores moves made in each game for replay and audit.
🔌
POST
/game/create
Request
{
  "playerXId": "string",
  "playerOId": "string"
}
Response
{
  "gameId": "string",
  "status": "string"
}
Creates a new game session with two players.
🔌
POST
/game/move
Request
{
  "gameId": "string",
  "playerId": "string",
  "row": "int",
  "col": "int"
}
Response
{
  "success": "boolean",
  "status": "string",
  "message": "string"
}
Player makes a move; returns success and updated game status.
🔌
GET
/game/replay
Request
{
  "gameId": "string"
}
Response
{
  "moves": "List of move objects with row, col, playerId, symbol, timestamp"
}
Retrieves all moves for replaying a completed game.

Practice

(1/5)
1. In the Hotel Management System, the RoomManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is this the correct choice?
easy
A. Prototype pattern, to clone RoomManager instances efficiently.
B. Factory pattern, because it controls object creation centrally.
C. Builder pattern, to construct complex RoomManager objects step-by-step.
D. Singleton pattern, to ensure only one instance manages room allocation.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single shared instance, which matches the Singleton pattern.
  2. Step 2: Why Singleton fits RoomManager

    RoomManager manages shared state like room availability; having multiple instances risks inconsistent state. Singleton ensures centralized control.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
2. In the Stack Overflow voting system, if the castVote() method is not synchronized, which exact sequence of thread interleaving causes a double vote count for the same user on a question?
medium
A. Thread 1 reads vote count, Thread 2 reads vote count, both increment and write back simultaneously
B. Thread 1 checks if user has voted -> false, context switch; Thread 2 checks if user has voted -> false; both proceed to cast vote
C. Thread 1 updates vote count, Thread 2 waits; Thread 1 finishes, Thread 2 updates vote count again
D. Thread 1 and Thread 2 both block on the same lock, causing deadlock

Solution

  1. Step 1: Identify the check-then-act race condition

    The critical issue is the check if the user has already voted. Without synchronization, both threads see no vote recorded and proceed.
  2. Step 2: Trace exact interleaving causing double vote

    Thread 1 checks voted -> false, context switch; Thread 2 checks voted -> false; both cast votes, causing double count.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-then-act gap without sync -> double vote [check]
Hint: Check-then-act gap -> double vote without sync [check]
Common Mistakes:
3. In a multi-threaded parking lot system, if parkVehicle() is NOT synchronized, which exact sequence causes double-booking of the same spot?
medium
A. Thread 1 parks vehicle, Thread 2 parks vehicle after Thread 1 finishes.
B. Thread 1 checks spot availability -> true, context switch; Thread 2 checks availability -> true; both park simultaneously.
C. Thread 1 parks vehicle, Thread 2 waits for Thread 1 to release lock before parking.
D. Thread 1 and Thread 2 both fail to find a spot and throw exceptions.

Solution

  1. Step 1: Identify the race condition

    Without synchronization, both threads can read spot as available before either marks it occupied.
  2. Step 2: Trace exact interleaving

    Thread 1: isAvailable() -> true, context switch before marking occupied; Thread 2: isAvailable() -> true, both proceed to park, causing double-booking.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-then-act gap without synchronization -> double-booking [check]
Hint: Check availability -> context switch -> both park -> double-booking [check]
Common Mistakes:
4. You want to add a WeekendFeeStrategy to calculate parking fees differently on weekends using the current Template Method design. What is the minimum change that respects SOLID principles?
medium
A. Modify the base class to add a weekend flag and conditional logic in the template method.
B. Add a boolean isWeekend field to all subclasses and override fee calculation accordingly.
C. Create a new class implementing FeeStrategy for weekends and inject it without changing existing classes.
D. Add weekend logic inside each subclass's fee calculation method, duplicating code.

Solution

  1. Step 1: Identify applicable SOLID principle

    Open/Closed Principle (OCP) requires extending behavior without modifying existing code.
  2. Step 2: Use strategy injection

    Creating a new FeeStrategy class for weekends and injecting it respects OCP and Dependency Inversion Principle (DIP).
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    New strategy class added; no existing class modified [check]
Hint: OCP: add new class, don't modify existing [check]
Common Mistakes:
5. In a parking lot system, if parkVehicle() is not synchronized, which exact sequence of Thread 1 and Thread 2 causes double-booking of the same spot?
medium
A. Thread 1 parks vehicle, Thread 2 waits until Thread 1 finishes, then parks another vehicle.
B. Thread 1 and Thread 2 both crash due to simultaneous access.
C. Thread 1 checks spot availability -> true, context switch; Thread 2 checks spot availability -> true; both park simultaneously.
D. Thread 1 parks vehicle, Thread 2 reads spot as occupied and waits.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 reads spot as available but before marking it occupied, context switches to Thread 2 which also reads it as available.
  2. Step 2: Both threads proceed to park, causing double-booking

    Because the state update is not atomic, both threads park vehicles in the same spot simultaneously.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Unsynchronized check-then-act -> double-booking race [check]
Hint: Check-then-act gap causes double-booking without synchronization [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard