Demystifying the Event Loop (featuring setTimeout)

Ryo Mac
Psynamic
Published in
7 min readSep 15, 2018

This article is my attempt to explain some fundamental aspects about asynchronous code, and how JavaScript works behind the scenes. So let’s get started with a handy visualization and some terminology before we go in-depth.

The Big Picture

There are many ways to visualize what’s going on here, but I especially like the following image because it gives a clear indication of what order things happen in, and provides some specific examples. It’s a good idea to refer back to this image as you read on.

Main components of a web browser, and what’s going on inside

Initial Terminology

Stack (AKA Call Stack): A data structure that keeps a record of where we are in the program. It pushes (i.e. adds) stuff onto the top of the stack, and pops (i.e. takes away) stuff from the top of the stack.

  • Data Structure: A format/structure for organizing, storing, and managing data (e.g. arrays, files, records, tables, trees, etc.).
  • Heap: A mainly unstructured region of memory (not important for this article… I only mention it because it is in the image above).

Web API: Client-side JavaScript (i.e. inside web browsers) which provides many APIs that aren’t native to the JavaScript language. For example, your browser presumably has a Geolocation API; which uses some complicated lower-level C++ code to allow you to retrieve location data. Other common browser APIs include the DOM (which represents webpages as nodes and objects), and APIs that get data from the server.

  • API (Application Programming Interface): APIs make life easier for developers by abstracting complex code away, much like how plugging something into an outlet is easier than physically wiring a device directly into a power supply. In addition to things like Geolocation APIs, certain devices also have Notification APIs, Vibration APIs, etc. There are also third-party APIs, which include Reddit’s API— allowing you to access posts from Reddit.com — and the Twilio API, allowing you actually send messages to someone’s phone.

Callback Queue (AKA Task/Event Queue): A data structure that stores tasks until it is sent to the stack (via the Event Loop) whenever the stack is empty.

Asynchronous tasks: Tasks which can be executed in the background, as opposed to those which must wait their turn before they can run (the ones that need to wait are called synchronous tasks).

The Story of the Event Loop

Order in the Call Stack

Stepping into a function pushes it onto the stack, whereas returning from a function pops it off (i.e. gets rid of it). So look at the following grossly (and gross) overly-simplistic code, and consider what order of each part (A, B, and C) is pushed onto the stack:

// A
const add = (a, b) => a + b;
// B
const explain = (a, b) => {
console.log("Your result is", add(a, b));
}
// C
explain(2, 3);

Although the add function (A) appears first in the code, it is only a definition, and therefore isn’t executed. It does not get called until the definition of the explain function (B). However, explain doesn’t get called until C. Therefore, C is pushed onto the stack first. Then the program sees that inside of B, A is called. B cannot conclude until A is done, so A is therefore pushed on top of the stack, even above B. Therefore, the order they’re put onto the stack is: C → B → ︎ A. If we have these functions inside a program called “main,” then here is a snapshot of how the stack would look after running main.

Since each of the lower tasks can’t run until the conclusion of higher ones, add (A) is resolved first (i.e. it is “on top” of the stack, so it is popped off the stack first). Once add is resolved and popped off the stack, explain will run next, outputting “Your result is 5.” Then main will finish running (since there’s no other code in the program), and the stack will then be empty. Once it’s empty, the Event Loop will activate.

Event Loop

The Event Loop is a mechanism that first waits for the stack to be empty. When it’s empty, the Event Loop pushes the first task from the callback queue (if one exists) onto the stack, so it can be executed. Depending on what that task is, that may set off a whole new cycle of events. For example, if you nested several setTimeouts inside of one another (which may indicate that you need a psychiatric evaluation), the stack would first empty, and then the event loop would grab the callback function that is waiting in the callback queue, and repeat the process until nothing is left inside the queue (and therefore, the stack).

Callback Queue

Asynchronous tasks (e.g. setTimeout(), AJAX requests) are sent first to the callback queue, where they might hang out for a bit. This allows the stack to continue executing tasks (because otherwise, it would block the program from running until whatever slow task is completed). When the stack is empty, the event loop, as explained above, sends the first thing in the queue to the stack.

This is a good time to talk about the concepts of FIFO (first in, first out) and LIFO (last in, first out). As described above, the stack executes the last (i.e. highest) thing to appear in the stack first. Therefore, the stack is “LIFO.”

However, the callback queue is a bit different. If tasks pile up in the queue, which task goes onto the stack? (Note: I sneakily already gave the answer above) Luckily, our the diagram above shows us very clearly — it is the first task (i.e. the left-most task) that the event loop sends to the stack. Therefore, the queue is actually “FIFO.”

Now let’s put all of this into practice…

Pop quiz (and explanation) with setTimeout

Question

The predetermined setTimeout function takes two arguments. The second argument is a number (of milliseconds), and the first argument is the callback function that will be executed after that designated number. Having explained that, a common question in tech interviews involves setTimeout() being set to zero. For example: What is the output of the function below?

const ohGodItsAnEventLoopQuestion = () => {
console.log(1);
setTimeout(() => { console.log(2); }, 1000);
setTimeout(() => { console.log(3); }, 0);
console.log(4);
}

ohGodItsAnEventLoopQuestion();

Take 30 seconds to answer this — you probably won’t need any longer. Either you know it or you don’t. (The answer is provided at the end — don’t scroll down until you try it yourself!)

Further Explanation & Answer

It’s true that JavaScript allows us to only do only one thing at a time (i.e. it’s a “single-threaded” language, which is like saying it has only one call stack)… but your browser includes numerous amazing Web APIs, such as the DOM, AJAX, and setTimeout. (Note: The Node equivalent to Web APIs are its C++ APIs, which can provide you with additional cool features)

Another visualization of the event loop in action

When an asynchronous function is called in the stack, it utilizes the appropriate Web API (i.e. setTimeout), and then the corresponding task (i.e. the console.log inside setTimeout) goes to the callback queue. The Event Loop then moves it to the stack where it can be executed.

For this reason, even when setTimeout is set to 0 (i.e. “wait for zero milliseconds”), the task will take the same journey mentioned in the previous paragraph (because setTimeout is a Web API), thereby coming after everything else in the stack is executed first. Why? Because as I mentioned earlier, the event loop only moves something from the callback queue to the stack if the stack is empty. So you can think of the event loop as something that moves tasks in the callback queue into an empty call stack.

In other words, even if your second argument in setTimeout is 0, it doesn’t actually mean “wait zero seconds to execute (i.e. execute immediately)”. Instead, it is more accurate to think of it as “wait a minimum of zero seconds, but execute whatever’s in the stack first.” For that reason, the answer to the interview question above is: 1 4 3 2.

Final Thoughts

This article included a lot of repetition because such an unintuitive concept may take beginners a long time to fully grasp (hence the fact that even senior developers get asked about this topic). But one effective way of learning something is to see multiple explanations of the same subject in different ways. For that reason, I encourage you to look elsewhere for other explanations or contexts if you need further explanation.

But I certainly hope this explanation gives you a good idea of the basics of how the event loop and setTimeout work… because I can almost guarantee that you will be asked about this during an interview at some point in your development career.

--

--