JavaScript Tech Interview Exercise 16: Stopwatch


Exercise: Create a stopwatch that counts down from a given number of seconds in the format mm:ss. Make it possible to start, pause, and reset the countdown. Make sure you can pass a callback function to the timer that is called when the displayed value is updated.

Solution: The main part of this exercise boils down to modeling. If you find the right model, your life will be easy. If you use the wrong model, implementation will not only be hard, but the stopwatch might not reflect reality. Let’s define the state space of the application:

  • countdownInitialValue: the number of seconds initially set on the timer when instantiating it. When we reset the timer, its value will be set to this value.
  • secondsLeft: the current value of the countdown timer in seconds

How do we make time pass?

The easy part of the answer is that we need to use the setInterval function.

setInterval( callback, delay ) executes callback every delay milliseconds. Therefore, in theory, we could come up with the following solution:

Surprisingly, this approach may be highly inaccurate. When resources are scarce, the setInterval call is delayed. Small delays add up throughout the course of a ten minute countdown. Imagine that you do a resource-intensive task, and your browser might completely lose focus and priority in the background. It may happen that in five seconds, your callback function is only called twice, resulting in a two second progress instead of five.

If you want accurate results, you can make use of the fact that Date.now() returns the current timestamp, yielding the number of milliseconds passed since January 1st, 1970. The battle plan is as follows:

  • We record the startTimestamp when we start the clock,
  • Whenever we are inside the setInterval callback, we retrieve the current timestamp and update secondsLeft,
  • The more often we run the setInterval call, the faster our results get updated. However, the results are independent from the delay value used in the setInterval call,
  • We pause the countdown by terminating the setInterval call
  • We resume at secondsLeft once we continue the countdown. Note we might ignore up to 999 milliseconds of elapsed time with this approach,
  • When reseting the counter, we equate secondsLeft to the initial value supplied in the constructor.

We will create an ES6 class to encapsulate all the necessary operations of the stopwatch:

The constructor initializes the state of the application and sends the initial value to the callback. The toString method converts the secondLeft property to mm:ss format, making sure that ss has a leading zero. Notice the ES2017 padStart function.

The start method implements the above described stopwatch algorithm. Both the pause and the reset method stops the clock, and reset moves the application state to its initial value. When pressing reset, we also have to call the display callback once.

You may think we are done with the exercise. Are we?

In every good exercise, there is a twist. Before reading any further, find a bug in the above implementation!

Welcome back! If you haven’t done the exercise, I encourage you to go back and do it, because this is how you get better.

If you are still reading, congratulations, you have found a bug!

My solution is that after starting the clock twice and pausing it, the clock is still running. We will therefore guard the start method in the following way: if this.interval is a number, we will not start the clock, as it is already started:

The reset methods executes pause, so we have to make sure that pause sets this.interval to a non-numeric value after clearing the interval. Therefore, we have to add a line to set this.interval to null.

Now we can test the code and it works even if we started the clock twice.

Learn ES6 in Practice

Sign up below to access an ES6 course with many exercises and reference solutions.