Synchronous vs Asynchronous Code
AI Using GitHub Copilot in VS Code
<aside> 💭
On this page we go deeper into how the JavaScript event loop works. You do not need to remember every detail for everyday programming. Focus on the big ideas: single thread, queues, and how Promises fit in.
</aside>
JavaScript has a single main thread of execution. That means it can only run one piece of JavaScript code at a time.
However, JavaScript programs still do a lot of things that look concurrent: timers, I/O, network requests, etc. The Event Loop is the mechanism that makes this possible.
When your code runs, JavaScript evaluates functions using a Call Stack:
If JavaScript only had a call stack, it could not handle long‑running operations without freezing. Instead, long‑running work (for example, a timer or network request) is usually offloaded to the host environment (for example, the browser or Node.js), and JavaScript is notified later when the result is ready.
Those “later” pieces of work are queued as tasks. The event loop’s basic job is:
This gives us the illusion of multiple things happening at once, while still keeping JavaScript single‑threaded.
Conceptually, each “tick” of the event loop looks like this:
Promises add another layer: microtasks.
When you attach handlers with .then() or .catch(), JavaScript does not run those handlers immediately when the promise settles. Instead, it schedules them as microtasks.
Conceptually there are at least two kinds of queues:
setTimeout()).then() / .catch() / .finally())The event loop processes them roughly like this:
The important consequence is:
When you write code like:
console.log('Hello!');
Promise.resolve().then(() => {
console.log('promise then');
});
console.log('Goodbye!');
the steps are:
console.log('Hello!') runs on the stack.Promise.resolve().then(...) schedules the .then callback as a microtask.console.log('Goodbye!') runs on the stack..then handler.So the output is:
Hello!
Goodbye!
promise then
Even though the promise is already resolved, its handler still goes through the microtask queue and does not interrupt the current synchronous code.
<aside> ⌨️
Check your understanding
If we add setTimeout(() => console.log('timeout'), 0); before the Promise.resolve().then(...), which line is printed first: "promise then" or "timeout"? Why?
</aside>
In the rest of this page we will use concrete examples to trace:
In a previous chapter we looked at an example where we appended the answer to the Ultimate Question of Life, the Universe, and Everything to log file. We will now revisit a simplified form of this example, to explore how this code works with the JavaScript event loop.
In this simplified example the answer promise is unconditionally resolved after two seconds whereafter the answer is written to the console.
function whatIsTheMeaningOfLife() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(42);
}, 2000);
});
}
function ask() {
let text = new Date().toLocaleTimeString('nl-NL') + ' ';
return whatIsTheMeaningOfLife()
.then((result) => {
text += `The answer is ${result}`;
})
.catch((err) => {
text += `Error: ${err.message}`;
})
.then(() => {
return console.log(text); // replaces: fsPromises.appendFile('answers.log', text + '\\n')
})
.catch((err) => {
console.error('Failed to write to log file:', err);
});
}
console.log('Hello!');
ask();
console.log('Goodbye!');
<aside> 💡
The callbacks of the two .then() methods and the first catch() hold a reference to the text variable outside of their own scope. They form closures.
</aside>
When you run this code the import line commented out as shown above, you will get this output similar to this:
Hello!
Goodbye!
19:40:48 The answer is 42
The first two lines are printed immediately, the next line with the answer 42 is printed after 2 seconds (2000 ms).
From this output we can conclude that not all code is executed in one go. There is no further code beyond the last console.log('Goodbye') statement, and when the JavaScript engine has executed that statement it has completed the current task. With that, it has nothing more to do and sits idle until the timer fires.
The video below may help to strengthen your understanding of the activities outlined above for the example code. The video is intended to illustrate the processes at the conceptual level. In reality, a lot more steps are happening behind the scenes, particularly in the call stack.
Visualization of the Event Loop for the Meaning of Life example (no audio). Graphics and animation inspired by the videos from Lydia Hallie (see Additional Resources below).
<aside> ⌨️
</aside>

In this example the answer promise is unconditionally rejected after two seconds.
function whatIsTheMeaningOfLife() {
return new Promise((resolve, reject) => {
setTimeout(
() =>
reject(new Error('Come back in 7.5 million years and ask me again!')),
2000
);
});
}
function ask() {
// Same as previous
}
console.log('Hello!');
ask();
console.log('Goodbye!');
In example 2 with the rejected promise, the sequence of events involving the timer task and subsequent microtasks is similar as explained in detail for example 1, but with these differences:
.then() is skipped.