The synchronous nature of JavaScript

Covering one of the most misunderstood and difficult-to-grasp concepts in JavaScript

Published May 9, 2022

A peek inside synchronous code

Whenever you're driving a car, it's not often that you think about what's happening under the hood; most people don't care — their only concern is whether the car runs or not. Similarly, a lot of developers approach JavaScript with the same mentality: "I don't care what's going on in the background, so long as my code runs."

Even though you're not expected to know everything, it is crucial to at least understand how JavaScript code gets executed. By default, JavaScript is a synchronous language, meaning that every line of code gets executed one-by-one. This makes it a "single-threaded" language. A good way to show this synchronous nature is through functions:


console.log('a');
console.log('b');
console.log('c');

/*
a
b
c
*/
        

...in JavaScript, normal functions are synchronous. console.log() is a function, that when invoked, prints out a message. Here, you can see three letters printed out in the same order they were invoked, as expected. Because JavaScript is synchronous, execution will always go something like this...

  1. JavaScript will reach the first function and invoke it, printing out a.
  2. JavaScript will move to the second function and invoke it, printing out b.
  3. JavaScript will move to the last function and invoke it, printing out c.

...no matter how many times this code snippet is executed, it'll always run in the same order, from top-to-bottom. This, in essence, is the nature of synchronous code.


Event queue and event loop

Since JavaScript is single-threaded, it can only execute one thing at a time. If you click a button on a website that does something, the thread will handle that task; if, after clicking that button, you immediately click another button, JavaScript will make a "mental note" to execute that task next. This process is referred to as the event queue. If there's tasks in the event queue, JavaScript will execute each one individually in corresponding order until the event queue is empty. Think of the event queue as a "to-do" list for the thread.

The process of continuously checking this "to-do" list is called the event loop. If the event queue is empty, the thread "goes to sleep", but once an event is fired, the thread starts working again. This synchronous behavior comes with a few problems. What if you wanna' fetch data from an API? An action like this might take a while; with synchronous code, everything after that request will have to wait until the data is received before it can continue executing the rest of the code. As you might imagine, this can drastically slow down performance. Luckily, this is where asynchronous JavaScript shines.


Asynchronous JavaScript

Asynchronous code is code that can start something now and finish it later; it governs how you perform tasks that take a while to do, such as fetching data from an API. In a nutshell, asynchronous code allows the program to be executed immediately, whereas synchronous code will block further execution of the remaining code until it finishes the current task. One example of asynchronous code in action is the setTimeout() function:


setTimeout(() => {
  console.log('a')
}, 2000);

console.log('b');
console.log('c');
        

...the setTimeout() function takes a function that'll execute after a specified amount of time. In this example, a will be printed out after 2 seconds. Someone with no understanding of asynchronous code might assume the execution will look like this...

  1. JavaScript will wait 2 seconds, then print a.
  2. b gets printed.
  3. c gets printed.

...but this assumption is incorrect. Since setTimeout() is an asynchronous function, JavaScript will queue the inner function and move on to execute the rest of the code; once 2 seconds have passed, JavaScript will return to that function and execute it, so in actuality, the execution ends up looking more like this...

  1. JavaScript sees that a should be executed after 2 seconds.
  2. JavaScript adds that task to the queue and moves on to execute the rest of the code.
  3. b gets printed.
  4. c gets printed.
  5. 2 seconds have passed. Time to go back and execute that queued task now.
  6. a gets printed.

...with asynchronous code, the thread pretty much says "I'm not gonna' sit here and wait for this — I got other things to do! I'll come back to this when it's ready." This ability to navigate the code freely is part of what makes asynchronous JavaScript so powerful.

That's the difference between synchronous and asynchronous code. Synchronous code is executed line-by-line, top-to-bottom, whereas asynchronous code can freely move to the next line of code before the previous one has executed. Asynchronous code is always "shoved aside" so synchronous code can run first.

For a more in-depth and visual explanation on the event loop, I highly recommend this amazing presentation by Philip Roberts: