All questions

Tetris

Premium

Tetris

Build a playable Tetris as a single React component. Tetris is the falling-block puzzle: tetrominoes (four-cell shapes) drop down a grid on a timer, you slide and rotate them as they fall, and completing a horizontal row clears it for points. It's a compact game that leans hard on three things React developers reach for constantly — a timer loop, keyboard input, and derived rendering — and it exposes the classic stale-closure trap when the loop reads state it didn't capture.

What you'll build

The starter App.tsx renders the board in its resting state — an empty 16-row by 10-column grid with the first piece resting at the top, score 0, and a Start button — with no logic. The piece definitions and an empty-board builder are already there. Make it playable:

  1. Hold the game in state. The board (16x10 of 0 or a color index 1..7), the active piece (four cells + a color), plus score, running, and over in useState.
  2. Run a gravity loop. On Start, setInterval(step, 600). Each step tries to move the piece down one row; if it can't, lock it into the board, clear any full rows, and spawn the next piece.
  3. Spawn in a fixed order. The seven pieces (I, O, T, S, Z, J, L) cycle in that exact order — no randomness — so every run is deterministic.
  4. Steer with the keyboard. A window keydown listener maps ArrowLeft/ArrowRight to a shift, ArrowDown to a soft-drop, and ArrowUp to a 90-degree rotation.
  5. Reject illegal moves. Any move — shift, drop, or rotate — is applied only if all four resulting cells stay on the board and off the settled blocks.
  6. Read the latest state. The tick and the key handler must act on the current board and piece — a value captured in the interval closure goes stale. Hold them in refs.
  7. Clean up. Clear the interval on game over and in the effect cleanup.

Examples

  • Load: the board is empty with the cyan I-piece across the top and score 0. Nothing falls until you press Start.
  • Press Start: the piece drops one row every 600ms. ArrowLeft/ArrowRight slide it, ArrowUp rotates it, ArrowDown drops it faster.
  • A piece that can't fall locks in place; a full row clears, the rows above drop down, and the score jumps by 100.
  • When a freshly spawned piece has nowhere to go, a Game Over overlay covers the board. Restart resets everything.

Notes

  • Separate the piece from the board. The settled board only changes when a piece locks; the active piece moves freely above it. Render the piece overlaid on the board.
  • Collision is one function. Off the board or onto a settled cell — the same check gates shifting, dropping, and rotating.
  • Rotation is a transform, not a lookup. Rotate each cell 90 degrees about the piece's pivot; if the result collides, keep the old piece.
  • The tick must read the latest state. A captured piece/board freezes at its first value — hold them in refs so the interval always sees the current game.
  • Always clear the interval. On game over and on unmount, or a stray loop keeps ticking after the component is gone.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium