Skip to the solution
MediumMachine Coding16 min read

Build an OTP Input

124 views

The problem

Question presented to candidate: "After sign-in we text the user a 6-digit code. Build the input for it: six separate boxes, one digit each. Typing should jump to the next box, Backspace should feel natural, and pasting the whole code from the clipboard must work. When all six digits are in, verify the code against the server."

Requirements:

  • Six single-digit boxes; non-digits are ignored.
  • Typing a digit moves focus to the next box; typing into a filled box replaces its digit.
  • Backspace clears the current box, or, on an empty box, clears and focuses the previous one. ArrowLeft and ArrowRight move between boxes.
  • Pasting (or SMS autofill dropping the whole code into one box) fills the boxes from the cursor, skipping spaces and dashes.
  • When the sixth digit lands, call a (simulated) verify API once; disable the boxes while it runs; on a wrong code, clear the boxes and focus the first one.
  • Accessible: a labelled group, a label per box, the numeric keypad on phones, and autocomplete="one-time-code".

What a strong answer should cover:

  • The state model: one array of strings as the single source of truth, plus refs to the DOM inputs used only to move focus.
  • Firing "complete" from the event handler that filled the last box, not from an effect that watches the array.
  • Keyboard edge cases: Backspace on an empty box, replacing a digit, ignoring letters, arrow keys.
  • Paste and autofill handling with preventDefault, and why maxLength={1} breaks them.
  • Accessibility: role="group" with a label, aria-label per box, inputMode="numeric", autocomplete="one-time-code", a role="status" message.
  • How the parent resets the widget after a wrong code (a key change in React and Vue, a method call through @ViewChild in Angular).

Clarifying questions expected:

  • "Digits only, or alphanumeric codes too? Is the length always 6?"
  • "Should we submit automatically on the last digit, or wait for a Verify button?"
  • "What should happen after a wrong code: keep the digits for editing, or clear them?"

Code / implementation expected: Yes. A working component in the candidate's framework with typing, Backspace, arrows, paste and a simulated verify call; tests or a driver script are a bonus.

  • React
  • Paste
  • Backspace

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

16 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 { useState } from "react";
import OtpInput from "./src/components/OtpInput";
import { DEMO_CODE, verifyCode } from "./src/api/fakeApi";

const IDLE = {
  kind: "idle",
  text: "Enter the 6-digit code we sent to your phone.",
};

export default function App() {
  const [status, setStatus] = useState(IDLE);
  // changing the key remounts a fresh, empty OtpInput
  const [attempt, setAttempt] = useState(0);

  async function handleComplete(code) {
    setStatus({ kind: "busy", text: "Verifying " + code + "..." });
    const ok = await verifyCode(code);
    if (ok)
      setStatus({ kind: "ok", text: "Code accepted. You are signed in." });
    else {
      setStatus({ kind: "error", text: "That code is not right. Try again." });
      setAttempt((n) => n + 1);
    }
  }

  return (
    <main className="page">
      <section className="card">
        <h1>Verify your phone</h1>
        <OtpInput
          key={attempt}
          length={6}
          disabled={status.kind === "busy" || status.kind === "ok"}
          onComplete={handleComplete}
        />
        <p
          className={"status " + status.kind}
          data-testid="otp-status"
          role="status"
        >
          {status.text}
        </p>
        <button
          type="button"
          className="link"
          onClick={() => {
            setStatus(IDLE);
            setAttempt((n) => n + 1);
          }}
        >
          Start over
        </button>
        <p className="demo">
          Demo: the right code is {DEMO_CODE} (the server is simulated).
        </p>
      </section>
    </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 73 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 Tooltip

Continue