AniUI Academy

Build a countdown timer

mediumState & Data+200 XP

Build a 5-second countdown with Start/Pause and Reset controls.

Pausing has to actually stop the countdown, and resuming has to continue from where it left off — not restart, and not keep ticking in the background while "paused".

Examples

// Initial: time-left = 5, button reads "Start"

// Click "Start"
// → each second: 4, 3, 2, 1, 0
// → button reads "Pause"

// Click "Pause" at 3
// → count holds at 3 indefinitely

// Click "Pause" (now "Start") again
// → count resumes 2, 1, 0 — not restarts at 3.

Pause/resume preserves the remaining seconds; reset snaps to 5 and stops.

useEffect(() => {
  if (!running) return;
  if (secondsLeft === 0) {
    setRunning(false);
    return;
  }
  const id = setInterval(() => {
    setSecondsLeft((s) => Math.max(0, s - 1));
  }, 1000);
  return () => clearInterval(id);
}, [running, secondsLeft]);

Gate the interval on `running`; the cleanup is what makes Pause actually stop ticking.

What it has to do

  • A `data-testid="time-left"` element shows the remaining whole seconds, starting at 5.
  • A `data-testid="start-pause"` button reads "Start" while stopped and "Pause" while running, and toggles between the two states.
  • While running, the count decreases by 1 every 1000ms.
  • While paused, the count does not change at all, however long you wait.
  • A `data-testid="reset"` button returns the count to 5 and stops the timer.

Notes

  • The classic bug: a stale interval kept ticking even while paused. Gate `setInterval` on the running flag, and let React's effect cleanup clear it on toggle.
  • For wall-clock accuracy under tab throttling, store a target end time and derive `secondsLeft = Math.max(0, (end - Date.now()) / 1000)` on each tick — a plain decrement drifts if the tab is backgrounded.
  • Stop the timer automatically when it hits 0 (set `running = false`) — otherwise the effect dependency churn creates and tears down the interval every re-render.
Asked atFlipkartZomatoSwiggy

Stuck? The react course covers the ideas this problem is built on.

import { useEffect, useState } from "react";

const START_SECONDS = 5;

export default function App() {
  const [secondsLeft, setSecondsLeft] = useState(START_SECONDS);
  const [running, setRunning] = useState(false);

  useEffect(() => {
    // TODO: this interval should only tick while `running` is true — right
    // now it counts down regardless of whether the timer is "paused".
    const id = setInterval(() => {
      setSecondsLeft((s) => Math.max(0, s - 1));
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return (
    <div style={{ fontFamily: "sans-serif", padding: 16 }}>
      <p style={{ fontSize: 32 }}>
        <span data-testid="time-left">{secondsLeft}</span>s
      </p>
      <button data-testid="start-pause" onClick={() => setRunning((r) => !r)}>
        {running ? "Pause" : "Start"}
      </button>
      <button
        data-testid="reset"
        onClick={() => {
          setSecondsLeft(START_SECONDS);
          setRunning(false);
        }}
        style={{ marginLeft: 8 }}
      >
        Reset
      </button>
    </div>
  );
}

5 tests · +200 XP

Discussion

Sign in to post
Sign in to join the conversation.Sign in

No comments yet. Be the first to say something.