Design a 2048 Game

Quick Overview

This question evaluates object-oriented design, state management, deterministic randomness handling, and algorithmic reasoning for implementing game rules on a configurable N x N grid.

Design a 2048 Game

Company: Asana

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Onsite

Design the object model and core game logic for a **2048 game** on an `N x N` grid. This is an object-oriented design problem: focus on the classes, their responsibilities, the relationships between them, and the algorithm for a move — not on the UI or rendering. For reference, 2048 is a sliding-tile puzzle. The player moves all tiles in one of four directions; tiles slide as far as they can, and two adjacent tiles of equal value merge into one tile of their sum. After every move that changes the board, a new tile spawns in a random empty cell. ### Constraints & Assumptions - The grid is square, `N x N`, with `N = 4` by default but configurable. - Tile values are powers of two; empty cells are represented by `0`. - A new tile is a `2` with probability $0.9$ and a `4` with probability $0.1$ (configurable). - The game starts with two random tiles already placed. - The default win condition is reaching a `2048` tile; the game may continue after winning. - The game logic must be deterministically testable — randomness has to be injectable so tests are reproducible. - Assume a single local player; no networking or persistence is required (but the design should not preclude them). ### The Problem Produce a class model and the core logic that satisfies the following requirements: - **Initialization** — start a board with two random tiles. - **Moves** — support `UP`, `DOWN`, `LEFT`, and `RIGHT`. - **Slide & merge** — implement the standard rules: tiles slide toward the move direction, and each tile may merge **at most once per move**. - **Scoring** — increase the score by the value of each merged tile. - **Tile spawn** — spawn a new tile **only** when a move actually changes the board. - **State detection** — detect the win state and the game-over state. - **Separation of concerns** — keep UI/presentation strictly out of the game logic. Also discuss how you would support these extensions without rewriting the merge logic: **undo**, custom board sizes, deterministic tests, and configurable spawn probabilities. ```hint Look for the repeated operation Before writing four separate move handlers, ask whether `UP`/`DOWN`/`RIGHT` are genuinely different from `LEFT`, or whether they're the same operation viewed from a different angle. If you can reduce them to one case, what's the smallest unit of the board that operation acts on? ``` ```hint The merge rule has a trap The "each tile merges at most once per move" rule is the easiest thing to get wrong. Pick a concrete line that exposes the ambiguity and decide its result *before* coding: what should `2 2 2` become when slid? Your answer here dictates exactly how your merge loop must advance. ``` ```hint Which pieces deserve to be pulled out? Two parts of this system are the ones you'll most want to test in isolation and vary later: the rule that's easy to get wrong, and the part that's non-deterministic. Consider what the `Game` should depend on so that neither of those is hardwired into it. ``` ```hint Game over isn't "board full" Watch out: a completely full board can still have a legal move. Think about what actually has to be true for *every* direction to be blocked at once — that condition, not "no empty cells," is your game-over test. ``` ### Clarifying Questions to Ask - Is the board always square, and is `N` fixed at 4 or configurable? - After reaching `2048`, does the game end immediately or can the player keep playing for a higher tile? - What are the exact spawn probabilities for `2` vs `4`, and must they be configurable? - Is undo a hard requirement, and if so, how many steps deep? - Do we need persistence (save/resume) or networking, or is this a single local session? - Should the score include only merge gains, or are there bonus/penalty rules? ### What a Strong Answer Covers - A clean class breakdown with single-responsibility classes and clear ownership of state vs. rules vs. randomness vs. presentation. - The "one line-merge function + grid transforms" insight that unifies the four directions. - Correct handling of the at-most-one-merge-per-move rule and the move-must-change-board-before-spawn rule. - Correct, non-trivial win **and** game-over detection (game-over ≠ board full). - Dependency injection of the random source for deterministic testing. - A concrete testing strategy (exhaustive line-merge cases, all four directions, seeded randomness, win/lose/undo integration). - How the chosen abstractions make the extensions (undo, board size, spawn config) cheap, and the time/space complexity of a move ($O(N^2)$). ### Follow-up Questions - How would you implement **undo** with minimal memory? Compare snapshotting the whole board vs. recording a reversible move delta, including how merges and the spawned tile complicate a pure inverse. - How would you detect game-over *efficiently* if `N` were large — can you maintain it incrementally instead of scanning the whole board after every move? - How would you adapt this design to a multiplayer or server-authoritative version where the server must validate moves and prevent cheating? - How would you generalize from "merge equal values into their sum" to a configurable rule (e.g., a different target tile, or Fibonacci-style merges) without touching the slide logic?

Overview: This question evaluates object-oriented design, state management, deterministic randomness handling, and algorithmic reasoning for implementing game rules on a configurable N x N grid.

|Home/Software Engineering Fundamentals/Asana
Asana logo
Asana
Apr 2, 2026
mediumSoftware EngineerOnsiteSoftware Engineering Fundamentals
83
0

Design the object model and core game logic for a 2048 game on an N x N grid. This is an object-oriented design problem: focus on the classes, their responsibilities, the relationships between them, and the algorithm for a move — not on the UI or rendering.

For reference, 2048 is a sliding-tile puzzle. The player moves all tiles in one of four directions; tiles slide as far as they can, and two adjacent tiles of equal value merge into one tile of their sum. After every move that changes the board, a new tile spawns in a random empty cell.

Constraints & Assumptions

  • The grid is square, N x N , with N = 4 by default but configurable.
  • Tile values are powers of two; empty cells are represented by 0 .
  • A new tile is a 2 with probability 0.90.9 and a 4 with probability 0.10.1 (configurable).
  • The game starts with two random tiles already placed.
  • The default win condition is reaching a 2048 tile; the game may continue after winning.
  • The game logic must be deterministically testable — randomness has to be injectable so tests are reproducible.
  • Assume a single local player; no networking or persistence is required (but the design should not preclude them).

The Problem

Produce a class model and the core logic that satisfies the following requirements:

  • Initialization — start a board with two random tiles.
  • Moves — support UP , DOWN , LEFT , and RIGHT .
  • Slide & merge — implement the standard rules: tiles slide toward the move direction, and each tile may merge at most once per move .
  • Scoring — increase the score by the value of each merged tile.
  • Tile spawn — spawn a new tile only when a move actually changes the board.
  • State detection — detect the win state and the game-over state.
  • Separation of concerns — keep UI/presentation strictly out of the game logic.

Also discuss how you would support these extensions without rewriting the merge logic: undo, custom board sizes, deterministic tests, and configurable spawn probabilities.

Clarifying Questions to Ask Guidance

  • Is the board always square, and is N fixed at 4 or configurable?
  • After reaching 2048 , does the game end immediately or can the player keep playing for a higher tile?
  • What are the exact spawn probabilities for 2 vs 4 , and must they be configurable?
  • Is undo a hard requirement, and if so, how many steps deep?
  • Do we need persistence (save/resume) or networking, or is this a single local session?
  • Should the score include only merge gains, or are there bonus/penalty rules?

What a Strong Answer Covers Guidance

  • A clean class breakdown with single-responsibility classes and clear ownership of state vs. rules vs. randomness vs. presentation.
  • The "one line-merge function + grid transforms" insight that unifies the four directions.
  • Correct handling of the at-most-one-merge-per-move rule and the move-must-change-board-before-spawn rule.
  • Correct, non-trivial win and game-over detection (game-over ≠ board full).
  • Dependency injection of the random source for deterministic testing.
  • A concrete testing strategy (exhaustive line-merge cases, all four directions, seeded randomness, win/lose/undo integration).
  • How the chosen abstractions make the extensions (undo, board size, spawn config) cheap, and the time/space complexity of a move ( O(N2)O(N^2) ).

Follow-up Questions Guidance

  • How would you implement undo with minimal memory? Compare snapshotting the whole board vs. recording a reversible move delta, including how merges and the spawned tile complicate a pure inverse.
  • How would you detect game-over efficiently if N were large — can you maintain it incrementally instead of scanning the whole board after every move?
  • How would you adapt this design to a multiplayer or server-authoritative version where the server must validate moves and prevent cheating?
  • How would you generalize from "merge equal values into their sum" to a configurable rule (e.g., a different target tile, or Fibonacci-style merges) without touching the slide logic?
Loading comments...