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 milliseconds until the SetInterval.prototype.clear method is called.

For example:

const runner = new SetInterval(() => console.log("hello"), 1000);

runner.start();

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

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

I was asked to use the following snippet as a template for my implementation:

function SetInterval(fn, interval) {}

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

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

This question opened me up to a lot of my flaws in understanding JavaScript.

The Solution

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 necessary for this implementation
      return setTimeout(wrapper, this.interval);
    }
    return;
  };
  wrapper();
};

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

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

theFn.start();

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

The 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 the SetInterval constructor.

The object’s toContinue property is set to track if executing this.fn at intervals should stop or not.

How does the start method work?

start () {
  const wrapper = () => {
    if(this.toContinue){
      this.fn()
      // returning the id in case it may be needed.
      // Not necessary for this 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.

When wrapper is executed, it checks if its instance’s toContinue property has a value of true. If the condition evaluates to true, this.fn is executed, else, it isn’t executed.

wrapper is set to be executed on the next this.interval milliseconds using setTimeout(wrapper, this.interval). setTimeout(wrapper, this.interval) stores the setTimeout function in JavaScript’s memory, and schedules wrapper to run in the next this.interval milliseconds.

How does the clear method work?

clear () {
  this.toContinue = false
}

When this.clear is executed, it sets the object’s toContinue property to a false. This causes the next execution of wrapper to find out that this.toContinue is now false, and then doesn’t execute this.fn or store another wrapper function to be executed in another this.interval milliseconds.

That is why the code snippet below:

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

theFn.start();

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

will log “hello” five times.

Clock Illustration