Skip to the solution
EasyMachine Coding8 min read

Build a Progress Bar

1.0k views

The problem

Question presented to candidate: "Build a ProgressBar component. It takes a value from 0 to 100 and shows a bar that fills smoothly as the value changes. Show several bars stacked on one page: one driven by a simulated upload, one you can nudge with buttons, one given an out-of-range value, and one for a task whose progress is not known yet."

Requirements:

  • value is clamped to 0..100 before use; NaN becomes 0. null (or missing) means unknown, which renders an indeterminate bar.
  • The fill animates with a CSS transition when the value changes (using transform, not width), and the animation respects prefers-reduced-motion.
  • Accessibility: role="progressbar" with an accessible name, aria-valuemin="0", aria-valuemax="100" and aria-valuenow set to the rounded clamped value; aria-valuenow is omitted while indeterminate. The percentage is also shown as text.
  • The demo's simulated upload adds 20 every 100 ms until it reaches 100, then stops its timer; the start button is disabled while it runs. Timers are cleaned up on unmount.
  • Several bars on one page are independent.

What a strong answer should cover:

  • Clamping as a pure function, and keeping the raw value separate from the displayed value.
  • The ARIA progressbar attributes, including the indeterminate case, and when the native <progress> element is a good alternative.
  • Why transform: scaleX() animates more cheaply than width, and reduced-motion support.
  • Timer lifecycle: deriving "running" from state, and cleaning up the interval on completion and on unmount (React StrictMode in particular).
  • Avoiding announcement spam for screen-reader users on fast updates.

Clarifying questions expected:

  • "Is the value always a percentage, or do we need a custom max such as bytes?"
  • "Should screen readers hear every update, or only milestones?"
  • "Do we need an indeterminate state?"

Code / implementation expected: Yes. A ProgressBar component and a demo page with the four bars described above.

  • React
  • UI
  • Animation

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

8 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 ProgressBar from "./src/components/ProgressBar";

export default function App() {
  const [upload, setUpload] = useState(0);
  const [started, setStarted] = useState(false);
  // raw value: the buttons may push it out of range
  const [manual, setManual] = useState(50);

  // Derived, not stored: the upload is running until it reaches 100.
  const running = started && upload < 100;

  // A simulated upload: +20 every 100 ms.
  // The interval only exists while running.
  useEffect(() => {
    if (!running) return;
    const id = setInterval(() => setUpload((v) => Math.min(v + 20, 100)), 100);
    // runs at 100, on unmount and in the StrictMode re-run
    return () => clearInterval(id);
  }, [running]);

  return (
    <main className="page">
      <h1>Progress</h1>

      <ProgressBar label="Upload" value={upload} />
      <div className="controls">
        <button
          type="button"
          onClick={() => {
            setUpload(0);
            setStarted(true);
          }}
          disabled={running}
        >
          {upload === 100 ? "Upload again" : "Start upload"}
        </button>
        <span className="note" data-testid="upload-status">
          {running ? "Uploading..." : upload === 100 ? "Done" : "Idle"}
        </span>
      </div>

      <ProgressBar label="Manual" value={manual} />
      <div className="controls">
        <button type="button" onClick={() => setManual((v) => v - 30)}>
          -30
        </button>
        <button type="button" onClick={() => setManual((v) => v + 30)}>
          +30
        </button>
        <span className="note" data-testid="manual-raw">
          raw value: {manual}
        </span>
      </div>

      <ProgressBar label="Bad input (140)" value={140} />
      <ProgressBar label="Waiting for server" value={null} />
    </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 8 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 nextEasy

Build a Toggle Switch

Continue