loading...
Cover image for Interview Question: Implement a Progress Bar

Interview Question: Implement a Progress Bar

healeycodes profile image Andrew Healey Originally published at healeycodes.com Updated on ・6 min read

I saw this question doing the rounds on social media. Apparently, top companies are using it to screen front end engineers. My fiancée will be applying to jobs soon and I asked her to give it a go. She almost completed the final stage (with a little research) but a tricky recursion bug tripped her up. I wrote this article to help her out. Hopefully, you'll find this useful should you face similarly themed questions!

There are a few variations but this question is normally asked in stages which get progressively harder.

1. Implement a loading bar that animates from 0 to 100% in 3 seconds

This can be done purely with CSS. If something can be done purely with CSS I tend to go for that option. My rationale is that it's easier to refactor something that is pure CSS than trying to extend a quick JavaScript hack. CSS is very declarative, it's easy to read and understand what's going on under the hood.

For a CSS only progress bar, I'm going to use two divs — a container and the progress bar — and keyframes. The important line here is animation: 1s linear fill;. There's a lot to talk about. What transition-timing-function are we going to use — ease, linear, a cubic-bezier?

At the very least, this quick answer shows that you know what keyframes and you can use it on a basic level.

<div class="container">
  <div class="progress-bar"></div>
</div>
.container {
  width: 300px;
  height: 50px;
  background-color: #D3D3D3;
}

.progress-bar {
  width: 100%;
  height: 50px;
  background-color: #90EE90;
  animation: 3s linear fill;
}

@keyframes fill {
    0% {
        width: 0%;
    }
    100% {
        width: 100%;
    }
}

2. Start loading bar animation upon a button click

Now we're moving into JavaScript land. I've always thought that transition was neat as heck so I'm going to use that with JavaScript to add a class to the progress-bar element. It enables you to "define the transition between two states of an element" (MDN).

I'm making sure to cache the reference to the loading bar. I work on Smart TVs a lot and caching is one of the many tricks we use to keep everything snappy. HTML elements have Element#classList which is a fantastic API for interacting with classes. It's very safe to use. You can add multiple classes and it will only add one instance, you can also remove classes that don't exist without any errors.

classList#toggle is particularly useful. "When only one argument is present: Toggle the class value; i.e., if the class exists then remove it and return false, if not, then add it and return true" (MDN).

<div class="container">
  <div class="progress-bar"></div>
</div>
<button onclick="loadBar()">Load</button>
.container {
  width: 300px;
  height: 50px;
  background-color: #D3D3D3;
}

.progress-bar {
  width: 0%;
  height: 50px;
  background-color: #90EE90;
  transition: width 3s linear;
}

.load {
  width: 100%;
}
const bar = document.querySelector('.progress-bar');

function loadBar () {
  bar.classList.add('load');
}

3. Queue multiple loading bars if the button is clicked more than once. Loading bar N starts animating with loading bar N-1 is done animating.

Here it gets more interesting. There is a way to iterate on our previous solution by removing and adding classes but that feels hacky. I think the intention is for you to use more JavaScript here. Technical interview questions don't really have a finish point. There's always restrictions, extensions, and what-ifs that can be thrown around. I'd love to see what you come up with before reading any further 👀.

There are a couple of traps here. You've got to make sure the time taken is exactly three seconds rather than a tick more or a tick less. And what is a good length for a tick? I suppose it depends on how wide your bar is. Going up by a percent each time seems like a sweet spot. Note: it's also better to manage your own state rather than relying on the DOM.

Caching is really important here. You don't want to traverse the DOM for an element 33 times a second. Two functions are probably required for clean code. I went for a recursive timeout with a global flag to track whether the progress bar is running. We want to add one to the queue either way but we don't want two going at once or our bar will load twice as quick!

<div class="container">
  <div class="progress-bar"></div>
</div>
<div>Queued bars: <span class="queued">0</span></div>
<button onclick="loadBar()">Load</button> 
.container {
  width: 300px;
  height: 50px;
  background-color: #D3D3D3;
}

.progress-bar {
  width: 0%;
  height: 50px;
  background-color: #90EE90;
}
const bar = document.querySelector('.progress-bar');
const queued = document.querySelector('.queued');

let loader = false;
let width = 0;
let count = 0;

function loadBar() {
  queued.innerText = ++count;
  if (loader === false) {
    bar.style.width = 0;
    tick();
  }
}

function tick() {
  loader = true;
  if (++width > 100) {
    queued.innerText = --count;
    width = 0;
    if (count < 1) {
      loader = false;
      return;
    }
  }
  bar.style.width = `${width}%`;
  setTimeout(tick, 30);
}

4. Do the same thing but without timers!

Okay, they don't really ask this in the interview but someone mentioned requestAnimationFrame in the comments on DEV and I thought it would be fun to build an example using it while emulating the previous answer.

The logic is a lot shorter if you don't have to queue bar loads. I ended up deciding to use two coupled functions. Recently, I saw someone say that any instance of else is your code is a chance for a refactor. I've been thinking it over, and while no rule holds true, it has been influencing how I shape functions lately. Check it out.

<div class="container">
  <div class="progress-bar"></div>
</div>
<div>Queued bars: <span class="queued">0</span></div>
<button onclick="loadBar(1)">Load</button> 
.container {
  width: 300px;
  height: 50px;
  background-color: #D3D3D3;
}

.progress-bar {
  width: 0%;
  height: 50px;
  background-color: #90EE90;
}
const bar = document.querySelector('.progress-bar');
const queued = document.querySelector('.queued');

let loading = false;
let count = 0;

function tick (timestamp, dist, duration) {
  const runtime = timestamp - starttime;
  let progress = runtime / duration;
  progress = Math.min(progress, 1);
  bar.style.width = `${dist * progress}%`;
  if (runtime > duration) {
    loading = false;
    count--;
    loadBar(0);
    return;
  }
  requestAnimationFrame (function(timestamp) {
      tick(timestamp, dist, duration)
  });
}

function loadBar (increment) {
  count += increment;
  queued.innerText = count;
  if (loading === true || count < 1) { return; }
  bar.style.width = 0;
  loading = true;
  requestAnimationFrame (function(timestamp) {
    starttime = timestamp;
    tick(timestamp, 100, 3000);
  });
}

End notes

Top marks if you have been shouting <progress> at me for the whole article. Yup, this element has been there since HTML5. You manage it by setting two attributes max and value. CSS Tricks have an article on how to style and animate it. They also cover the different states, determinate and indeterminate — the latter meaning "progress unknown". These states are great because they give us a native way to communicate with the user.

This interview question is less about the perfect answer and more about how you communicate your thoughts as you go and the clarifications you ask about. Should there also be a numerical representation of percentage? Is this running on a low-powered device? If so, don't go up in one percent increments. Perhaps five or ten is better.

I think a good extension might be to ask an interviewee to build an interface that receives a WebSocket message describing the progress state and communicates that to the user.

What do you think of this interview question? Does it meet at the crossroads of problem-solving and browser knowledge for you?


Join 150+ people signed up to my newsletter on programming and personal growth!

I tweet about tech @healeycodes.

Discussion

pic
Editor guide
Collapse
angeliquejw profile image
Angelique

It would be great to see a solution that also incorporates some of the ARIA markup to make the progressbar accessible for all users and the JS to handle that, too:
codepen.io/angeliquejw/pen/LoJYER

Collapse
healeycodes profile image
Andrew Healey Author

That's very true. Great solution 👍

ggenya132 profile image
Eugene Vedensky

I'm with you, breadth of knowledge is important. That is certainly what my role demands of me and I like it, but in being broad I think we sacrifice depth in some of these categories. I think this particular test is incredibly general and is well suited to many full stack developers, I agree with the author that extending it to a sockets implementation would make it even more fun.

Collapse
johnlandgrave profile image
John Landgrave

For the final option, there's still another option that is likely more peformant?

Using the transitionend event would allow you to use the code from the second example and then just set the width of the inner bar back to 0 if the queued length > 0.

This prevents you from having to setTimeout, manually animate, and have that width value be global-ish.

Collapse
healeycodes profile image
Andrew Healey Author

This is an awesome idea. I suspected that something like this would be possible. It definitely sounds more performant.

I knew the community would have some cool ideas about this problem. I’d love to see this coded up ☺️

Collapse
steveblue profile image
Steve Belovarich

It would make a huge difference if the time requirement was removed completely and the candidate was asked to architect a progress bar based off some sort of input value that is variable. This would actually require some level of problem solving and the interviewer could observe how the problem is solved. It is also based off a real problem, could start a discussion of event based architectural patterns like Redux, and reflects component based design patterns.

Collapse
healeycodes profile image
Andrew Healey Author

I agree! It does seem a little far removed from a real world problem. I think the time aspect is so that setTimeout/Interval is used, which introduces some basic scoping problems.

Collapse
steveblue profile image
Steve Belovarich

FWIW given the conditions of the test if someone were to use setInterval I wouldn’t be that impressed. requestAnimationFrame would be way better and even more so Web Animations API. Even then the test is still hunting for a very specific answer that amounts to trivia which should be avoided in test scenarios.

Thread Thread
healeycodes profile image
Andrew Healey Author

That's a great point Steve. I went ahead and added a requestAnimationFrame version too! 😊

Collapse
kurisutofu profile image
kurisutofu

Nice.
I'm ashamed to say I'd have failed that interview as I rarely think of animations in CSS ...

Collapse
healeycodes profile image
Andrew Healey Author

That's one aspect that seems unfair about this question. Remembering the correct animation syntax on the spot.

Collapse
healeycodes profile image
Andrew Healey Author

Awesome! It's great to see a solution using React 😊. Those four colors are a nice mix.

Collapse
ggenya132 profile image
Eugene Vedensky

See I think it depends on the role. If you're hiring a css oriented front-end developer, that's fine. If you're brining in my JS architecture oriented folks, the kind who are more adept at setting up a babel rc than a keyframe, well then it's unfair.

Collapse
luisdanielmesa profile image