The Single Threaded Reality

JavaScript is a single-threaded, synchronous language. To understand asynchronicity, we first have to master its synchronous foundations.

April 25, 20264 min read1 / 5

After mastering the metaprogramming funnels of coercion, we're ready to tackle the architectural feature that makes the modern web possible: Asynchronous JavaScript.

Promises, the event loop, and microtask queues are often seen as "magic" to a fault. But they are built on a very strict, very predictable foundation. To understand how they work, we must first remember how JavaScript runs code by default.

The Essentials

  1. Single Thread: JavaScript can only do one thing at a time. It executes code line-by-line.
  2. Synchronous Execution: Every line of code must finish before the next one starts.
  3. The Engine: Comprised of the Thread of Execution, Global Memory, and the Call Stack.

The Core Model: A Quick Review

When JavaScript starts running, it creates a Global Execution Context. This consists of two main parts: the thread of execution and memory.

Let's trace a simple, synchronous snippet:

JavaScript
const num = 3; function multiplyBy2(input) { const result = input * 2; return result; } const output = multiplyBy2(num); const newOutput = multiplyBy2(10);

As we move through this code, our Thread of Execution goes line-by-line:

  1. Line 1: Declare num in global memory and assign it 3.
  2. Line 2: Declare function multiplyBy2 and store its entire definition.
  3. Line 7: Declare output. Before we can assign it a value, we must run multiplyBy2(3).
    • A new Execution Context is created and pushed onto the Call Stack.
    • Inside, the parameter input is assigned 3.
    • result becomes 6.
    • The return keyword sends 6 back to global memory.
    • The context is popped off the stack and deleted.
  4. Line 8: Declare newOutput. Again, we must run the function and finish it before we can move on.
JavaScript Execution Engine
Thread of Execution
1const num = 3;
2function multiplyBy2(input) {
3 const result = input * 2;
4 return result;
5}
6const output = multiplyBy2(num);
7const newOutput = multiplyBy2(10);

Step 1:Global memory stores num as 3.

Memory
num3
Call Stack
Global
Bottom of Stack

The "Blocking" Problem

This model is super predictable. But it has a fatal flaw: it is blocking.

Imagine if instead of multiplyBy2, we had a function getVideosFromTikTok. If that function had to speak to a server in West Virginia to get links, it might take 300 milliseconds. In the world of high-speed computing, 300ms is an eternity.

Because JavaScript is single-threaded, if we ran that function synchronously, no other code could run while we waited. The user interface would freeze. Buttons wouldn't click. Animations would stop.

The Solution: JavaScript is Not Alone

JavaScript does not run in isolation. It runs inside a Web Browser (or a Node environment). The browser is not single-threaded. It has a whole suite of powerful features that are not part of JavaScript itself:

  • The DOM: The model of the webpage.
  • Network Requests: Speaking to the internet (fetch).
  • Timers: Keeping track of time (setTimeout).
  • Console: Logging to dev tools.

Facade Functions

To access these features, we use Facade Functions. They look like regular JavaScript functions (like setTimeout or fetch), but they are actually portals to the browser's background features.

In the next part, we'll see how we can "spin up" these browser features to do work in the background without blocking our single thread.

Further Reading and Watching