Skip to the code
cf.

setInterval with setTimeout

EasyTiming

Write mySetInterval(fn, delay, ...args) and myClearInterval(id) without using setInterval.

mySetInterval calls fn(...args) every delay ms, the first time after delay ms, until it's cleared. It returns an ID. Several intervals can run at once, each with its own ID.

myClearInterval(id) stops the interval with that ID for good. It must also work when fn calls it to stop its own interval.

let count = 0;
const id = mySetInterval((label) => {
  count++;
  console.log(label, count);
  if (count === 3) myClearInterval(id);
}, 1000, "tick");
// logs "tick 1", "tick 2", "tick 3", a second apart, then stops

What the tests check

  1. calls fn every delay ms
    const clock = useFakeTimers();
    const fn = spy();
    mySetInterval(fn, 100);
    clock.tick(99);
    expect(fn).toHaveBeenCalledTimes(0);
    clock.tick(1);
    expect(fn).toHaveBeenCalledTimes(1);
    clock.tick(100);
    expect(fn).toHaveBeenCalledTimes(2);
    clock.tick(300);
    expect(fn).toHaveBeenCalledTimes(5);
    
  2. passes the extra arguments to fn
    const clock = useFakeTimers();
    const fn = spy();
    mySetInterval(fn, 50, "a", 1);
    clock.tick(100);
    expect(fn).toHaveBeenCalledTimes(2);
    expect(fn.calls[1].args).toEqual(["a", 1]);
    
  3. myClearInterval stops it, even after several runs
    const clock = useFakeTimers();
    const fn = spy();
    const id = mySetInterval(fn, 100);
    clock.tick(300);
    myClearInterval(id);
    clock.tick(1000);
    expect(fn).toHaveBeenCalledTimes(3);
    
  4. myClearInterval works from inside fn
    const clock = useFakeTimers();
    let id;
    const fn = spy(() => {
      if (fn.callCount === 2) myClearInterval(id);
    });
    id = mySetInterval(fn, 100);
    clock.tick(1000);
    expect(fn).toHaveBeenCalledTimes(2);
    
  5. keeps several intervals apart
    const clock = useFakeTimers();
    const a = spy();
    const b = spy();
    const idA = mySetInterval(a, 100);
    mySetInterval(b, 150);
    clock.tick(300);
    myClearInterval(idA);
    clock.tick(300);
    expect(a).toHaveBeenCalledTimes(3);
    expect(b).toHaveBeenCalledTimes(4);
    
  6. works without setInterval
    const clock = useFakeTimers();
    const fakeSetInterval = globalThis.setInterval;
    globalThis.setInterval = () => {
      throw new Error("mySetInterval must not use setInterval");
    };
    const fn = spy();
    try {
      const id = mySetInterval(fn, 100);
      clock.tick(250);
      myClearInterval(id);
      clock.tick(250);
    } finally {
      globalThis.setInterval = fakeSetInterval;
    }
    expect(fn).toHaveBeenCalledTimes(2);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).