The Call Stack

To understand how JavaScript processes background tasks, you first need a quick refresher on how it processes foreground tasks using the Call Stack.

Every time a function is called, it gets added (pushed) to the top of the call stack. When the function finishes executing and returns, it gets removed (popped) off the stack.

Visualizing the Stack

Let’s look at a sequence of synchronous function calls:

function startJob() {
  console.log("Job started");
  workOnJob();
}

function workOnJob() {
  console.log("Working on job");
  finishJob();
}

function finishJob() {
  console.log("Job finished");
}

startJob();

As the code executes, the call stack grows layer by layer:

  1. [empty]
  2. [startJob] (Calls workOnJob)
  3. [startJob] -> [workOnJob] (Calls finishJob)
  4. [startJob] -> [workOnJob] -> [finishJob]

Then, as each nested function finishes executing, it is popped off the stack in reverse order:

  1. finishJob returns -> pops off.
  2. workOnJob returns -> pops off.
  3. startJob returns -> pops off.
  4. [empty]

JavaScript’s call stack works the exact same way as any other traditional programming language. But what happens when we introduce asynchronous code? We need a secondary queue.