What this prompt is really asking
Scope · special moves
Game · Board · Piece
pseudo-legal → legal
castle · undo · bot
Clarifying questions → locked requirements
- You: Two human players, standard rules, backend only? They: Yes — no UI, no network matchmaking.
- You: Must we support castling, en passant, and promotion in v1? They: Promotion yes (queen is fine); castling / en passant can be extensions.
- You: Detect check, checkmate, stalemate? They: Yes for the three terminal/status cases.
- You: Reject illegal moves without corrupting state? They: Yes — wrong turn, empty square, blocked path, leaves king in check.
Entities: Game, Board, Piece, Move, Player
- Game — Board, two Players,
current_turn,status, move history. Entry point:make_move(from, to). - Board — 8×8 of nullable Pieces.
get/set/remove, path-clear helpers for sliding pieces,find_king,is_square_attacked. - Piece (abstract) — color +
pseudo_legal_moves(board, pos)orcan_move_to(...). Subclasses: King, Queen, Rook, Bishop, Knight, Pawn. - Move — from, to, optional captured piece, optional promotion type. Useful for history / undo / notation later.
- Player — name + color. Pure data (no AI inside Player).
Class design from the requirements
from abc import ABC, abstractmethod
from enum import Enum, auto
from typing import Optional
class Color(Enum):
WHITE = auto()
BLACK = auto()
class GameStatus(Enum):
ACTIVE = auto()
CHECK = auto()
CHECKMATE = auto()
STALEMATE = auto()
class Position:
def __init__(self, row: int, col: int): # 0..7
self.row, self.col = row, col
class Move:
def __init__(self, frm: Position, to: Position,
captured: Optional["Piece"] = None,
promotion: Optional[type] = None):
self.frm, self.to = frm, to
self.captured, self.promotion = captured, promotion
class Piece(ABC):
def __init__(self, color: Color):
self.color = color
@abstractmethod
def pseudo_legal_moves(self, board: "Board", pos: Position) -> list[Position]:
...
class Board:
SIZE = 8
def __init__(self):
self.grid: list[list[Optional[Piece]]] = [
[None] * self.SIZE for _ in range(self.SIZE)
]
# setup_standard_position()
def get(self, p: Position) -> Optional[Piece]: ...
def place(self, p: Position, piece: Optional[Piece]) -> None: ...
def is_path_clear(self, frm: Position, to: Position) -> bool: ...
def find_king(self, color: Color) -> Position: ...
def is_attacked(self, pos: Position, by: Color) -> bool: ...
class Player:
def __init__(self, name: str, color: Color):
self.name, self.color = name, color
class Game:
def __init__(self, white: Player, black: Player):
self.board = Board()
self.white, self.black = white, black
self.current = Color.WHITE
self.status = GameStatus.ACTIVE
self.history: list[Move] = []
def make_move(self, frm: Position, to: Position,
promotion: Optional[type] = None) -> bool: ...
def legal_moves(self, frm: Position) -> list[Position]: ...
import java.util.*;
enum Color { WHITE, BLACK }
enum GameStatus { ACTIVE, CHECK, CHECKMATE, STALEMATE }
class Position {
int row, col; // 0..7
Position(int row, int col) { this.row = row; this.col = col; }
@Override public boolean equals(Object o) {
if (!(o instanceof Position)) return false;
Position p = (Position) o;
return row == p.row && col == p.col;
}
@Override public int hashCode() { return Objects.hash(row, col); }
}
class Move {
Position frm, to;
Piece captured; // nullable
Class<? extends Piece> promotion; // nullable
Move(Position frm, Position to, Piece captured, Class<? extends Piece> promotion) {
this.frm = frm; this.to = to;
this.captured = captured; this.promotion = promotion;
}
}
abstract class Piece {
Color color;
Piece(Color color) { this.color = color; }
abstract List<Position> pseudoLegalMoves(Board board, Position pos);
}
class Board {
static final int SIZE = 8;
Piece[][] grid = new Piece[SIZE][SIZE]; // null = empty
// setupStandardPosition()
Piece get(Position p) { /* ... */ return null; }
void place(Position p, Piece piece) { /* ... */ }
boolean isPathClear(Position frm, Position to) { /* ... */ return false; }
Position findKing(Color color) { /* ... */ return null; }
boolean isAttacked(Position pos, Color by) { /* ... */ return false; }
}
class Player {
String name;
Color color;
Player(String name, Color color) { this.name = name; this.color = color; }
}
class Game {
Board board = new Board();
Player white, black;
Color current = Color.WHITE;
GameStatus status = GameStatus.ACTIVE;
List<Move> history = new ArrayList<>();
Game(Player white, Player black) {
this.white = white; this.black = black;
}
boolean makeMove(Position frm, Position to, Class<? extends Piece> promotion) { /* ... */ return false; }
List<Position> legalMoves(Position frm) { /* ... */ return List.of(); }
}
Implementation highlights
make_move.# --- Knight: geometry only (no path; jumps) ---
class Knight(Piece):
DELTAS = ((1, 2), (1, -2), (-1, 2), (-1, -2),
(2, 1), (2, -1), (-2, 1), (-2, -1))
def pseudo_legal_moves(self, board, pos):
out = []
for dr, dc in self.DELTAS:
r, c = pos.row + dr, pos.col + dc
if not board.in_bounds(r, c):
continue
dest = Position(r, c)
occ = board.get(dest)
if occ is None or occ.color != self.color:
out.append(dest) # empty or capture
return out
# --- Game: pseudo-legal → legal (king safety) ---
def legal_moves(self, frm: Position) -> list[Position]:
piece = self.board.get(frm)
if piece is None or piece.color != self.current:
return []
legal = []
for to in piece.pseudo_legal_moves(self.board, frm):
if self._is_safe_after(frm, to):
legal.append(to)
return legal
def _is_safe_after(self, frm: Position, to: Position) -> bool:
piece = self.board.get(frm)
captured = self.board.get(to)
self.board.place(to, piece)
self.board.place(frm, None)
king_pos = self.board.find_king(self.current)
safe = not self.board.is_attacked(king_pos, _opponent(self.current))
self.board.place(frm, piece) # revert
self.board.place(to, captured)
return safe
def make_move(self, frm: Position, to: Position,
promotion: Optional[type] = None) -> bool:
if self.status in (GameStatus.CHECKMATE, GameStatus.STALEMATE):
return False
if to not in self.legal_moves(frm):
return False
piece = self.board.get(frm)
captured = self.board.get(to)
self.board.place(to, piece)
self.board.place(frm, None)
if isinstance(piece, Pawn) and (to.row == 0 or to.row == 7):
self.board.place(to, (promotion or Queen)(piece.color))
self.history.append(Move(frm, to, captured, promotion))
self.current = _opponent(self.current)
self.status = self._compute_status()
return True
def _compute_status(self) -> GameStatus:
# side to move = self.current
any_legal = any(
self.legal_moves(pos)
for pos in self.board.positions_of(self.current)
)
in_check = self.board.is_attacked(
self.board.find_king(self.current), _opponent(self.current)
)
if not any_legal:
return GameStatus.CHECKMATE if in_check else GameStatus.STALEMATE
return GameStatus.CHECK if in_check else GameStatus.ACTIVE
// --- Knight: geometry only (no path; jumps) ---
class Knight extends Piece {
static final int[][] DELTAS = {
{1, 2}, {1, -2}, {-1, 2}, {-1, -2},
{2, 1}, {2, -1}, {-2, 1}, {-2, -1}
};
Knight(Color color) { super(color); }
List<Position> pseudoLegalMoves(Board board, Position pos) {
List<Position> out = new ArrayList<>();
for (int[] d : DELTAS) {
int r = pos.row + d[0], c = pos.col + d[1];
if (!board.inBounds(r, c)) continue;
Position dest = new Position(r, c);
Piece occ = board.get(dest);
if (occ == null || occ.color != this.color) {
out.add(dest); // empty or capture
}
}
return out;
}
}
// --- Game: pseudo-legal → legal (king safety) ---
List<Position> legalMoves(Position frm) {
Piece piece = board.get(frm);
if (piece == null || piece.color != current) return List.of();
List<Position> legal = new ArrayList<>();
for (Position to : piece.pseudoLegalMoves(board, frm)) {
if (isSafeAfter(frm, to)) legal.add(to);
}
return legal;
}
boolean isSafeAfter(Position frm, Position to) {
Piece piece = board.get(frm);
Piece captured = board.get(to);
board.place(to, piece);
board.place(frm, null);
Position kingPos = board.findKing(current);
boolean safe = !board.isAttacked(kingPos, opponent(current));
board.place(frm, piece); // revert
board.place(to, captured);
return safe;
}
boolean makeMove(Position frm, Position to, Class<? extends Piece> promotion) {
if (status == GameStatus.CHECKMATE || status == GameStatus.STALEMATE) return false;
if (!legalMoves(frm).contains(to)) return false;
Piece piece = board.get(frm);
Piece captured = board.get(to);
board.place(to, piece);
board.place(frm, null);
if (piece instanceof Pawn && (to.row == 0 || to.row == 7)) {
try {
Class<? extends Piece> cls = promotion != null ? promotion : Queen.class;
board.place(to, cls.getConstructor(Color.class).newInstance(piece.color));
} catch (Exception e) {
board.place(to, new Queen(piece.color));
}
}
history.add(new Move(frm, to, captured, promotion));
current = opponent(current);
status = computeStatus();
return true;
}
GameStatus computeStatus() {
// side to move = current
boolean anyLegal = false;
for (Position pos : board.positionsOf(current)) {
if (!legalMoves(pos).isEmpty()) { anyLegal = true; break; }
}
boolean inCheck = board.isAttacked(board.findKing(current), opponent(current));
if (!anyLegal) return inCheck ? GameStatus.CHECKMATE : GameStatus.STALEMATE;
return inCheck ? GameStatus.CHECK : GameStatus.ACTIVE;
}
Verification trace
- Initial: White to move, status=ACTIVE.
- White e2→e4: pawn pseudo-legal includes e4; simulate → king safe → apply; turn=Black.
- Black tries e7→e5 similarly.
- Illegal: White e4→e6 (pawn can’t jump two after first move / wrong geometry) → rejected.
- Self-check: after a sequence that pins a piece, that piece’s pseudo-legal capture along the pin is filtered out by
_is_safe_after. - Mate: when side to move has zero legal moves and king is attacked → CHECKMATE; further
make_movereturns False.
Extensibility follow-ups
- Castling — store castling rights on Game (or per-king/rook moved flags). King pseudo-legal includes ±2 files when rights + path clear + squares not attacked;
make_movealso moves the rook. - En passant — remember last double-pawn push as an ep target square; pawn pseudo-legal may include that capture; clear the captured pawn off the adjacent file on apply.
- Undo / history — Move as Command: store captured + rights + ep target; undo pops and restores. Same stack feeds PGN export later.
- Bot opponent — keep Player as data;
BotEngine.choose_move(game) -> Movesearches legal moves (minimax / alpha-beta). Don’t shove AI into the Piece hierarchy. - Chess960 / fairy pieces — new Piece subclass + setup; Game’s legality filter stays untouched if you respected the split.
Common interview pitfalls
These mistakes show up constantly on this prompt. Name the trap, then show the fix in your design — don’t wait for the interviewer to catch you.
- Implementing full FIDE rules in 35 minutes — never finishes.
- Missing Piece polymorphism — giant switch on type in Game.
- Confusing pseudo-legal moves with legal moves (king left in check).
- No concept of side to move / castling rights called out as deferred.
- Mutating board during move generation without undo/copy discipline.
- UI-driven piece classes that know about sprites.
Interview script (say this)
Read this once out loud before a mock. It’s the spine of a strong answer — not a script to recite robotically.
- Scope hard: 8×8, six piece types, turns, check detection; defer en passant/castling if needed but name them.
- Piece ABC with pseudo_legal_moves(board, pos); King/Pawn special cases.
- Game.try_move: filter legal = pseudo-legal where own king not in check after apply.
- Trace: moving pinned piece illegally rejected; checkmate detection sketch.
- Staff: AI engine as consumer of legal move generator.
Extra verification traces
Walk these three traces on the board. If you can narrate them cleanly, your implementation section usually follows.
def legal_moves(self, color):
moves = []
for pos, piece in self.board.pieces(color):
for dest in piece.pseudo_legal(self.board, pos):
snapshot = self.board.copy()
snapshot.apply(pos, dest)
if not snapshot.in_check(color):
moves.append((pos, dest))
return moves
List<MovePair> legalMoves(Color color) {
List<MovePair> moves = new ArrayList<>();
for (Map.Entry<Position, Piece> e : board.pieces(color)) {
Position pos = e.getKey();
Piece piece = e.getValue();
for (Position dest : piece.pseudoLegal(board, pos)) {
Board snapshot = board.copy();
snapshot.apply(pos, dest);
if (!snapshot.inCheck(color)) {
moves.add(new MovePair(pos, dest));
}
}
}
return moves;
}
Staff-level follow-ups
At staff+, they twist the prompt. Answer in one sentence that names the seam — don’t redesign the whole board.
- Full rules? — Incremental rule modules (CastlingRights, EnPassantSquare) on BoardState.
- Engine? — Search over legal_moves; evaluation separate.
- Variants Chess960? — SetupFactory for initial positions; Piece movement reused.
- Clocks? — Clock service observing move events — Game emits MoveMade.
Complete solution notes (Chess)
from threading import Lock
class GameService:
def __init__(self):
self.games: dict[str, object] = {}
self._locks: dict[str, Lock] = {}
self._map_lock = Lock()
def _lock_for(self, game_id: str) -> Lock:
with self._map_lock:
return self._locks.setdefault(game_id, Lock())
def make_move(self, game_id: str, *args):
with self._lock_for(game_id):
return self.games[game_id].make_move(*args)
# Different game ids → different locks → parallel matches.
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
class GameService {
Map<String, Object> games = new HashMap<>();
Map<String, ReentrantLock> locks = new HashMap<>();
private final ReentrantLock mapLock = new ReentrantLock();
ReentrantLock lockFor(String gameId) {
mapLock.lock();
try {
return locks.computeIfAbsent(gameId, k -> new ReentrantLock());
} finally {
mapLock.unlock();
}
}
Object makeMove(String gameId, Object... args) {
ReentrantLock lock = lockFor(gameId);
lock.lock();
try {
// return games.get(gameId).makeMove(...);
return null;
} finally {
lock.unlock();
}
}
}
// Different game ids → different locks → parallel matches.
Concurrency cases
- Two HTTP moves on one game — without per-game lock, turn checks race.
- Spectators reading board — copy board under lock or accept briefly stale reads.
- Matchmaking — separate lock from game rules lock.