Skip to the solution
MediumFrontend11 min read

Build Grid Lights

306 views

The problem

Question presented to candidate: "Build a small grid of lights, 3 by 3 with the middle cell empty. Clicking a cell turns it on. When every light is on, they should turn off one by one in the reverse of the order they were clicked, one every 300 milliseconds. While they are turning off, clicks should be ignored."

Requirements:

  • Render the grid from a config array (1 = light, 0 = empty slot); the default is a 3 x 3 ring with a hole in the centre.
  • Clicking an off light turns it on; clicking a light that is already on does nothing.
  • When all lights are on, deactivate them in reverse activation order at a fixed 300 ms interval.
  • Ignore clicks during deactivation; after the last light turns off, the grid accepts clicks again.
  • Clean up timers if the component unmounts.

What a strong answer should cover:

  • Storing the activation ORDER as the state (an array of indices) so "is it on" and "which goes off next" are both derived from it.
  • The deactivation as a stack: last in, first out, one pop per tick.
  • Timer choice and cleanup: a chained setTimeout per step or one setInterval, cleared on completion and on unmount.
  • A deactivating flag (or derived phase) that locks input, and why it is needed.
  • Accessibility: buttons with aria-pressed for on/off, a label per cell, and aria-disabled while locked.

Clarifying questions expected:

  • "Is the interval fixed at 300 ms, and should the first light go off immediately or after one interval?"
  • "Can the grid shape change, for example a 4 x 4 config?"
  • "Should a click during deactivation be ignored, or should it cancel the sequence?"

Code / implementation expected: Yes. A working grid with the timed reverse deactivation; expect questions about timer cleanup.

  • Grid
  • Timers
  • State

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

11 min read ยท 9 sections

React solution

Every file of the working solution. Open it in the playground to run and change it.

ReactComplete solution
import { useEffect, useState } from "react";
import Light from "./src/components/Light";
import {
  CONFIG,
  OFF_DELAY_MS,
  activate,
  countLights,
  deactivateLast,
  slotsFrom,
} from "./src/data/lights";

const SLOTS = slotsFrom(CONFIG);
const TOTAL = countLights(CONFIG);

export default function App() {
  // indices, in the order they were turned on
  const [order, setOrder] = useState([]);
  const [deactivating, setDeactivating] = useState(false);

  // One timeout per step: each run turns off ONE
  // light, then the new `order` schedules the next.
  // The cleanup clears a pending timeout, so
  // StrictMode and unmounts cannot leave one behind.
  useEffect(() => {
    if (!deactivating) return;
    if (order.length === 0) {
      setDeactivating(false);
      return;
    }
    const id = setTimeout(() => setOrder(deactivateLast), OFF_DELAY_MS);
    return () => clearTimeout(id);
  }, [deactivating, order]);

  function handleActivate(index) {
    if (deactivating || order.includes(index)) return;
    const next = activate(order, index);
    setOrder(next);
    if (next.length === TOTAL) setDeactivating(true);
  }

  return (
    <main className="lights-app">
      <h1>Grid Lights</h1>
      <p className="hint" data-testid="status" aria-live="polite">
        {deactivating
          ? "Turning off in reverse order..."
          : order.length + " of " + TOTAL + " on"}
      </p>
      <div
        className="grid"
        style={{ gridTemplateColumns: "repeat(" + CONFIG[0].length + ", 1fr)" }}
      >
        {SLOTS.map((slot) => (
          <Light
            key={slot.index}
            slot={slot}
            on={order.includes(slot.index)}
            locked={deactivating}
            onActivate={handleActivate}
          />
        ))}
      </div>
    </main>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 63 of 107 in Machine coding

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

Build a Form with validation

Continue