Back to Machine Coding
Machine Coding
easy
mid

How would you build a capital and country matching game in React?

Multiple-choice quiz: given a country, pick the right capital from 4 options. State: questions list, current index, selected answer, score. Generate distractors from other countries' capitals (random, avoid the answer). Show feedback after select, then advance. End with a score summary + restart. Keyboard support and accessibility (radiogroup).

3 min read·~25 min to think through

A capital-country game is a tight machine-coding round: data, randomization, state machine, and feedback UI in ~80 lines.

1. Data

js
const COUNTRIES = [
  { country: "France", capital: "Paris" },
  { country: "Japan", capital: "Tokyo" },
  // ...
];

2. Question generation

For each round, pick a country and 3 distractor capitals from other countries. Shuffle the 4 options.

js
function makeQuestions(data, count = 10) {
  const shuffled = [...data].sort(() => Math.random() - 0.5);
  return shuffled.slice(0, count).map((entry) => {
    const distractors = shuffled
      .filter((d) => d.capital !== entry.capital)
      .sort(() => Math.random() - 0.5)
      .slice(0, 3)
      .map((d) => d.capital);
    const options = [...distractors, entry.capital].sort(() => Math.random() - 0.5);
    return { country: entry.country, answer: entry.capital, options };
  });
}

(Math.random() - 0.5 is biased; Fisher-Yates is correct. Mention it.)

3. State machine

ts
asking → answered (correct/wrong) → next | finished
jsx
function Game() {
  const [questions] = useState(() => makeQuestions(COUNTRIES, 10));
  const [i, setI] = useState(0);
  const [selected, setSelected] = useState(null);
  const [score, setScore] = useState(0);

  const q = questions[i];
  const done = i >= questions.length;

  const pick = (opt) => {
    if (selected) return;                 // lock after answering
    setSelected(opt);
    if (opt === q.answer) setScore((s) => s + 1);
  };

  const next = () => {
    setSelected(null);
    setI((x) => x + 1);
  };

  if (done) return <Summary score={score} total={questions.length} onRestart={() => location.reload()} />;

  return (
    <fieldset>
      <legend>What is the capital of {q.country}?</legend>
      {q.options.map((opt) => {
        const isAnswer = selected && opt === q.answer;
        const isWrong = selected === opt && opt !== q.answer;
        return (
          <label key={opt}>
            <input
              type="radio"
              name="opt"
              value={opt}
              disabled={!!selected}
              checked={selected === opt}
              onChange={() => pick(opt)}
            />
            {opt} {isAnswer && "✓"} {isWrong && "✗"}
          </label>
        );
      })}
      {selected && <button onClick={next}>{i === questions.length - 1 ? "Finish" : "Next"}</button>}
    </fieldset>
  );
}

4. Feedback

After selection:

  • Correct option highlighted green.
  • Wrong selection highlighted red.
  • Options disabled — you can't change your mind.
  • "Next" button appears.

5. Score summary

jsx
function Summary({ score, total, onRestart }) {
  return (
    <div>
      <h2>You scored {score} / {total}</h2>
      <button onClick={onRestart}>Play again</button>
    </div>
  );
}

6. Accessibility

  • <fieldset> + <legend> for the question — proper radio group semantics.
  • Labels wrapping inputs (clickable).
  • Don't rely on color alone for feedback — add icon or text.
  • Announce correctness via a polite live region.
  • Keyboard: arrow keys to navigate options (built-in for radio groups), Enter to pick.

7. The polish

  • Timer per question with auto-fail on timeout.
  • Streak bonus.
  • Difficulty levels (more distractors).
  • Persistent high score (localStorage).
  • Reverse mode — given a capital, pick the country.
  • Hints (continent / population).
  • Fisher-Yates shuffle instead of biased sort(() => Math.random() - 0.5).

Interview framing

"Generate N questions upfront: for each, pick a country and 3 distractor capitals from other entries (avoid duplicates), shuffle the 4 options. State machine is asking → answered → next, with a score counter. After selecting, lock the options, show feedback (color + icon, not color-only), then enable Next. End screen shows score and a restart. Use <fieldset>+<legend> + labeled radios for a proper radio group with built-in keyboard nav. Fisher-Yates for the shuffle — Math.random() - 0.5 is biased."

Follow-up questions

  • Why is `sort(() => Math.random() - 0.5)` biased?
  • How would you persist score and add a leaderboard?
  • How would you add a timer per question?
  • Why use fieldset/legend for the question?

Common mistakes

  • Biased shuffle.
  • Allowing re-answering after seeing feedback.
  • Picking the answer as a distractor (duplicate option).
  • Color-only feedback.
  • Re-randomizing distractors per render.

Performance considerations

  • Trivial — small data. Memoize the questions array (generated once on mount). Avoid regenerating distractors on every render.

Edge cases

  • Country with multiple acceptable answers.
  • Fewer than 4 distractors available.
  • Empty data set.

Real-world examples

  • Duolingo-style multiple-choice quizzes, Sporcle, geography apps.

Senior engineer discussion

Seniors get the small details right: lock after answering, deterministic shuffle (Fisher-Yates), proper radio group semantics, non-color-only feedback, and avoid re-randomizing on every render.

Related questions