Implement a multi-rover Mars controller
Company: Shopify
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Technical Screen
Quick Answer: This question evaluates a candidate's ability to implement stateful command parsing, entity management for multiple rovers, and collision-avoidance on an unbounded 2D grid, emphasizing deterministic simulation, input/output specification, and robust handling of invalid operations.
Constraints
- Grid is unbounded; coordinates may be negative.
- Rover IDs are unique non-empty strings.
- Commands are case-insensitive for the keyword (CREATE/M/etc.); IDs are case-sensitive.
- Collision is enforced only on M (rovers may overlap at the (0,0) spawn cell).
- Each command produces exactly one output line; blank lines are skipped.
Examples
Input: (['CREATE a', 'SELECT a', 'L', 'M', 'M'],)
Expected Output: ['created a', 'selected a: (0, 0) N', '(0, 0) W', '(-1, 0) W', '(-2, 0) W']
Explanation: v1 single rover: create+select a (starts (0,0) N). L turns it to face West (no move). Two M's walk it west to (-1,0) then (-2,0). Demonstrates turning and negative coordinates on the unbounded grid.
Input: (['CREATE a', 'SELECT a', 'M', 'R', 'M', 'CREATE b', 'SELECT b', 'M', 'SELECT a', 'M'],)
Expected Output: ['created a', 'selected a: (0, 0) N', '(0, 1) N', '(0, 1) E', '(1, 1) E', 'created b', 'selected b: (0, 0) N', '(0, 1) N', 'selected a: (1, 1) E', '(2, 1) E']
Explanation: The prompt's example interaction. a -> (0,1) N, turns R, moves to (1,1) E. b created+moved to (0,1). Re-selecting a and moving: a faces East at (1,1), target (2,1) is free (b is at (0,1)), so it moves freely to (2,1) -- the final M does NOT block because the cells don't line up.
Input: (['CREATE b', 'SELECT b', 'M', 'CREATE a', 'SELECT a', 'M'],)
Expected Output: ['created b', 'selected b: (0, 0) N', '(0, 1) N', 'created a', 'selected a: (0, 0) N', '(0, 0) N [BLOCKED]']
Explanation: v3 collision: b moves north to occupy (0,1). a spawns at (0,0) facing N; its target (0,1) is occupied by b, so M fails -- a stays at (0,0) N and the line is marked [BLOCKED].
Input: (['M', 'SELECT ghost', 'DELETE ghost', 'CREATE a', 'CREATE a'],)
Expected Output: ['ERROR: no rover selected', "ERROR: no such rover 'ghost'", "ERROR: no such rover 'ghost'", 'created a', "ERROR: rover 'a' already exists"]
Explanation: v2 invalid-operation contract: M with no selection errors; SELECT and DELETE of a non-existent id error; creating a duplicate id errors. No state is mutated on any error.
Input: (['CREATE a', 'SELECT a', 'DELETE a', 'M'],)
Expected Output: ['created a', 'selected a: (0, 0) N', 'deleted a', 'ERROR: no rover selected']
Explanation: Deleting the currently selected rover clears the selection, so the subsequent M reports 'no rover selected' instead of dereferencing a dangling id.
Input: (['CREATE a', 'SELECT a', 'M', 'M', 'CREATE b', 'SELECT b', 'M'],)
Expected Output: ['created a', 'selected a: (0, 0) N', '(0, 1) N', '(0, 2) N', 'created b', 'selected b: (0, 0) N', '(0, 1) N']
Explanation: Freed-cell case: a walks north off (0,1) to (0,2), so (0,1) is no longer occupied. b then moves north into the now-free (0,1) successfully -- occupancy correctly releases the old cell on each move.
Input: (['CREATE a', 'SELECT a', 'R', 'R', 'R', 'R', 'L', 'M'],)
Expected Output: ['created a', 'selected a: (0, 0) N', '(0, 0) E', '(0, 0) S', '(0, 0) W', '(0, 0) N', '(0, 0) W', '(-1, 0) W']
Explanation: Direction arithmetic: four R turns cycle N->E->S->W->N (full circle back to North). One L then faces West; M moves to (-1,0). Confirms modular turn handling in both directions.
Input: ([],)
Expected Output: []
Explanation: Edge case: an empty command stream produces no output.
Hints
- Model direction as an index 0=N,1=E,2=S,3=W so a right turn is (d+1)%4 and a left turn is (d-1)%4 -- no if/elif ladder, no sign bugs.
- Keep a (dx,dy) delta table per direction (N=(0,1), E=(1,0), S=(0,-1), W=(-1,0)) as the single source of truth for movement.
- Separate per-rover state {(x,y,facing)} from controller-level state (selection + an occupancy map cell->rover_id). Only the controller can see the whole grid, so collision logic lives there.
- On a successful M, free the rover's old cell in the occupancy index before claiming the new one; on a blocked M, change nothing and append [BLOCKED].
- Handle the error branches explicitly: no selection for L/R/M, unknown/duplicate ids for CREATE, missing ids for SELECT/DELETE, and clearing selection when the selected rover is deleted.