Back to Machine Coding
Machine Coding
easy
mid

How would you build an OTP input component?

An array of N single-char inputs. Key behaviors: auto-advance focus on entry, backspace moves to the previous box, paste fills all boxes at once, accept only digits, expose the joined value. Manage refs in an array, keep state as a string[].

5 min read·~25 min to think through

An OTP input looks trivial — N tiny boxes — but interviewers are testing focus management, paste handling, and keyboard UX.

Core data model

jsx
function OTPInput({ length = 6, onComplete }) {
  const [values, setValues] = useState(Array(length).fill(""));
  const refs = useRef([]);

  const focusIndex = (i) => refs.current[i]?.focus();

  const handleChange = (i, e) => {
    const digit = e.target.value.replace(/\D/g, "").slice(-1); // keep last digit only
    if (!digit) return;
    const next = [...values];
    next[i] = digit;
    setValues(next);
    if (i < length - 1) focusIndex(i + 1);          // auto-advance
    if (next.every(Boolean)) onComplete?.(next.join(""));
  };

  const handleKeyDown = (i, e) => {
    if (e.key === "Backspace" && !values[i] && i > 0) {
      focusIndex(i - 1);                            // backspace on empty → go back
    }
    if (e.key === "ArrowLeft" && i > 0) focusIndex(i - 1);
    if (e.key === "ArrowRight" && i < length - 1) focusIndex(i + 1);
  };

  const handlePaste = (e) => {
    const digits = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, length);
    if (!digits) return;
    e.preventDefault();
    const next = Array(length).fill("");
    [...digits].forEach((d, idx) => (next[idx] = d));
    setValues(next);
    focusIndex(Math.min(digits.length, length - 1));
    if (next.every(Boolean)) onComplete?.(next.join(""));
  };

  return (
    <div onPaste={handlePaste}>
      {values.map((v, i) => (
        <input
          key={i}
          ref={(el) => (refs.current[i] = el)}
          value={v}
          onChange={(e) => handleChange(i, e)}
          onKeyDown={(e) => handleKeyDown(i, e)}
          inputMode="numeric"
          maxLength={1}
          aria-label={`Digit ${i + 1}`}
        />
      ))}
    </div>
  );
}

What interviewers grade

  • Auto-advance — typing moves focus forward; the most expected behavior.
  • Backspace on empty — moves to the previous box (and clears it). Without this, the component feels broken.
  • Paste — users paste the whole code from an SMS. Handle onPaste, distribute digits, focus the right box.
  • Input sanitization — strip non-digits; inputMode="numeric" brings up the numeric keypad on mobile.
  • Refs in an arrayrefs.current[i] set via the callback ref pattern.
  • Accessibilityaria-label per box, autocomplete one-time-code so iOS autofills.

The framing

"I model it as a string[] of length N with a parallel array of refs. The four behaviors that make it feel real: auto-advance on type, backspace-on-empty to go back, full paste distribution, and digit-only sanitization. The trap is treating each box as independent — focus management is the whole problem."

Follow-up questions

  • How do you handle pasting a 6-digit code into the first box?
  • How would you support autocomplete='one-time-code' for iOS SMS autofill?
  • How do you make this accessible to screen readers?
  • How would you add a countdown timer and resend button?

Common mistakes

  • Forgetting paste handling — users paste codes from SMS.
  • Backspace doesn't move focus back, so correcting a digit is painful.
  • Not sanitizing input — letters or multiple chars land in a box.
  • Storing refs incorrectly instead of an array indexed by position.
  • No onComplete callback, so the parent can't react when all boxes are filled.

Performance considerations

  • Trivially cheap — N is small (4–6). The only concern is avoiding re-creating the refs array on every render; useRef holds it stably.

Edge cases

  • Pasting more digits than boxes — slice to length.
  • Pasting fewer digits — fill what you can, focus the next empty box.
  • Mobile keyboards inserting non-digit characters.
  • User clicks into the middle box first — handle out-of-order entry.

Real-world examples

  • 2FA/login code entry in banking and auth flows.
  • Email verification code screens.

Senior engineer discussion

Seniors treat it as a focus-management problem, not N independent inputs. They handle paste first (the most-missed requirement), wire backspace-on-empty, sanitize input, add autoComplete='one-time-code', and expose a clean onComplete contract to the parent.

Related questions