JavaScript is run in the browser, but the browser itself contains much more than just a JavaScript runtime. If you’ve ever used a feature like setTimeout() but didn’t really understand how it works and why it can lead to unexpected behavior such as with for-loops then read on to learn more about the event loop and what’s really going on under-the-hood in a modern web browser.

Another option is simply to watch this excellent 26-minute talk by Philip Roberts where he covers the same content in a visual fashion. In fact, maybe you should just watch the video first and then read this article after to make sure everything was solidified.

Still with me? Ok, let’s continue.

JavaScript in the browser

One of the great things about JavaScript is that it runs directly in the browser. There’s no setup required, you can run code just by opening a new browser tab and using the JavaScript Console.

But within the web browser there’s a lot more to JavaScript than just a runtime engine. In fact there’s also a robust list of Web APIs, a callback queue, and an event loop.

Therefore when we think about how JavaScript code executes in a browser, there are four distinct areas to cover:

  • JavaScript runtime engine
  • Web APIS provided by the browser like the DOM, setTimeout, etc.
  • a callback queue for events with callbacks like onClick and onLoad
  • an event loop

This visual from the video above illustrates the point nicely:

Image of JS in the browser

JavaScript runtime engine

The runtime engine is how the browser actually compiles and then executes our JavaScript code. Each browser uses its own engine, but the best known is (V8), which is used by Chrome and also powers NodeJS!

Web APIs

The browser does a lot more than just execute JavaScript, it also provides developers with a rich set of Web APIs like the DOM and methods like setTimeout(). In fact, if you simply type window in your console you can scroll through the long, long list of APIs included by default.

Result of window in the JS console

Call stack

JavaScript is a single-threaded language which means it has a single call stack. A call stack is a data structure where the last record entered is the first returned. Think of it like a stack of pancakes; you can add many pancakes onto a stack but when it comes time to eat the pancakes, you start at the top with the last pancake added and work your way down. The key takeaway is that JavaScript can only do one thing at at time.

As a result, if we have a stack of operations to perform and one of them is really slow, everything else has to wait until the slow operation completes. This is called blocking. Things that are slow in a browser include network requests, image processing, or a while loop from 1 to 1,000,000,000.

Real-world implications

Consider what happens when using setTimeout in a for loop. It seems like the output of this should be 1 2 3 4.

for (var i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 1000)  
// 5 5 5 5

But in fact it’s 5 5 5 5.

Read about SetTimeout in a For Loop for a detailed explanation why that touches upon the call stack in action.