I was in a job interview pair programming session where I was asked to implement JavaScript’s setInterval method without using setInterval itself. I did poorly at that interview for many reasons, including not knowing how to implement this.

The question

Implement the setInterval function in such a way that executing new SetInterval.prototype.start will start the function passed to it and run the function at intervals interval until the SetInterval.prototype.clear method is called.

For example:

const theIntervalRunner = new SetInterval(() => console.log("logging"), 1000);

theIntervalRunner.start();

setTimeout(() => theIntervalRunner.clear(), 5000);

will log logging 5 times on the console/terminal at an interval of 1000ms.

Use this as a template

function SetInterval(fn, interval) {}

SetInterval.prototype.start = function () {};

SetInterval.prototype.clear = function () {};

I had not encountered a situation where I needed to implement something like this prior to the interview. I was also too tensed to see that I could have interpreted the SetInterval function as a JavaScript class constructor in my mind, and then implementing it would have been easier. Notwithstanding, this question opened me up to a lot of my flaws in understanding JavaScript.

My solution (after the interview)

After reading Michael Zheng’s implementation at Implement setInterval with setTimeout, I modified his implementation to fit the interview question.

function SetInterval(fn, interval) {
  this.fn = fn.bind(this);
  this.interval = interval;
  this.toContinue = true;
}

SetInterval.prototype.start = function () {
  const wrapper = () => {
    if (this.toContinue) {
      this.fn();
      // returning the id in case it may be needed.
      // Not really necessary for this particular implementation
      return setTimeout(wrapper, this.interval);
    }
    return;
  };
  wrapper();
};

SetInterval.prototype.clear = function () {
  this.toContinue = false;
};

const theFn = new SetInterval(function () {
  console.log("logging");
}, 1000);

theFn.start();

setTimeout(() => {
  theFn.clear();
}, 5000);

You can copy this and run it in your browser console to test it 😌

Explanation

Interpret the SetInterval function as a JavaScript class for easier understanding. See it as

class SetInterval {
  constructor(fn, interval) {
    this.fn = fn.bind(this);
    this.interval = interval;
    this.toContinue = true;
  }
}

When a new SetInterval object is created via new SetInterval(fn, interval), the function fn that will run, and the interval are both passed in. The value of this in the function argument fn passed in, is replaced with the object instance that will be created by the SetInterval constructor function using bind. This is done so that any reference to this in fn will refer to the object instance created by SetInterval. The result of executing bind is passed to this.fn, which is fn but with its this referring to the object created by SetInterval.

The object’s toContinue property is set to track when the running of this.fn at intervals should stop.

start () {
  const wrapper = () => {
    if(this.toContinue){
      this.fn()
      // returning the id in case it may be needed.
      // Not really necessary for this particular implementation
      return setTimeout(wrapper, this.interval);
    }
    return;
  };
  wrapper()
}

start is a method on object instances created by SetInterval. When start is executed, a reference to wrapper is first stored in start’s local memory, and then wrapper is executed.

On executing wrapper, the object’s toContinue property is checked for a truthy value. For a truthy value, this.fn is executed wrapper is set to be executed on the next this.interval milliseconds using setTimeout(wrapper, this.interval). This line in particular stores the setTimeout function in JavaScript’s memory for events, and schedules wrapper to run in the next this.interval milliseconds.

How do we clear/stop the interval function executions?

clear () {
  this.toContinue = false
}

clear is also a method on object instances created by invoking SetInterval as a constructor. Executing this.clear sets the object’s toContinue property to a falsy value. When executed, the next execution of wrapper through the setTimeout in start finds out that this.toContinue is currently falsy, and then doesn’t execute this.fn or store another wrapper function to be executed in another this.interval milliseconds.

That’s why the code snippet below

const theFn = new SetInterval(function () {
  console.log("logging");
}, 1000);
theFn.start();

setTimeout(() => {
  theFn.clear();
}, 5000);

will log logging five times.

Clock Illustration