Tools: How JavaScript Async Actually Works (Event Loop, Micro tasks, and Call Stack) (2026)

Tools: How JavaScript Async Actually Works (Event Loop, Micro tasks, and Call Stack) (2026)

1. JavaScript Is Single-Threaded

2. What Is the Call Stack?

3. The Problem: Long-Running Tasks

4. Web APIs

5. Task Queue

6. Event Loop

7. Full Async Flow

8. Promise Is Special

9. Microtasks vs Tasks

Task Queue (Macrotasks)

Microtask Queue

10. Example

11. Event Loop Order (Important)

12. What async Really Does

13. What await Really Does

14. Mental Model of await

15. await Splits Execution

16. Another Example

17. Key Insight About await

18. Sequential vs Parallel

19. Why This Matters

20. Big Picture

21. Final Summary If you have ever thought: then you are ready to understand how JavaScript async really works. When I first struggled with “why a Promise is returned”, I realized that I could not go further without understanding the internal mechanism. In JavaScript, async behavior is built on these five concepts: Promise and async / await are just built on top of this system. Let’s break it down step by step. JavaScript can execute only one thing at a time. This order is controlled by the Call Stack. a stack that keeps track of currently executing functions It follows LIFO (Last In, First Out). The stack grows as functions are called and shrinks as they finish. Why does setTimeout run later, even with 0ms? This is where Web APIs come in. JavaScript engines (like V8) do not handle timers or network requests. are provided by the browser (or runtime environment). When the timer finishes, the callback is not executed immediately. It is placed into the: Task Queue (Macrotask Queue) This is a queue of functions waiting to be executed. a mechanism that checks if the call stack is empty Here is the important part: Promise.then() does NOT use the normal task queue. It uses a different queue called: JavaScript has two queues: Microtasks run before tasks Because microtasks run first. The real execution order: This is the core rule. async = function that returns a Promise await splits the function into two parts using Promise.then Everything after await becomes a microtask. await does NOT block the thread splits the function and schedules the rest as a microtask Parallelism depends on when the Promise starts, not when you await it both start immediately. JavaScript async is built on: The most important ideas: Once you understand this, you can explain: async / await stops being magic and becomes predictable. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or

Code Block

Copy

console.log("A") console.log("B") console.log("C") CODE_BLOCK: console.log("A") console.log("B") console.log("C") CODE_BLOCK: console.log("A") console.log("B") console.log("C") CODE_BLOCK: A B C CODE_BLOCK: function main() { a() } function a() { b() } function b() { console.log("hello") } main() CODE_BLOCK: function main() { a() } function a() { b() } function b() { console.log("hello") } main() CODE_BLOCK: function main() { a() } function a() { b() } function b() { console.log("hello") } main() CODE_BLOCK: 1 main() 2 a() 3 b() 4 console.log() CODE_BLOCK: 1 main() 2 a() 3 b() 4 console.log() CODE_BLOCK: 1 main() 2 a() 3 b() 4 console.log() COMMAND_BLOCK: console.log("start") setTimeout(() => { console.log("timeout") }, 0) console.log("end") COMMAND_BLOCK: console.log("start") setTimeout(() => { console.log("timeout") }, 0) console.log("end") COMMAND_BLOCK: console.log("start") setTimeout(() => { console.log("timeout") }, 0) console.log("end") CODE_BLOCK: start end timeout CODE_BLOCK: start end timeout CODE_BLOCK: start end timeout COMMAND_BLOCK: console.log("A") setTimeout(() => { console.log("B") }, 0) console.log("C") COMMAND_BLOCK: console.log("A") setTimeout(() => { console.log("B") }, 0) console.log("C") COMMAND_BLOCK: console.log("A") setTimeout(() => { console.log("B") }, 0) console.log("C") CODE_BLOCK: A C B COMMAND_BLOCK: console.log("start") setTimeout(() => { console.log("timeout") }, 0) Promise.resolve().then(() => { console.log("promise") }) console.log("end") COMMAND_BLOCK: console.log("start") setTimeout(() => { console.log("timeout") }, 0) Promise.resolve().then(() => { console.log("promise") }) console.log("end") COMMAND_BLOCK: console.log("start") setTimeout(() => { console.log("timeout") }, 0) Promise.resolve().then(() => { console.log("promise") }) console.log("end") CODE_BLOCK: start end promise timeout CODE_BLOCK: start end promise timeout CODE_BLOCK: start end promise timeout CODE_BLOCK: async function test() { return 1 } CODE_BLOCK: async function test() { return 1 } CODE_BLOCK: async function test() { return 1 } CODE_BLOCK: Promise.resolve(1) CODE_BLOCK: Promise.resolve(1) CODE_BLOCK: Promise.resolve(1) CODE_BLOCK: async function main() { console.log("A") const value = await Promise.resolve(1) console.log("B") } main() console.log("C") CODE_BLOCK: async function main() { console.log("A") const value = await Promise.resolve(1) console.log("B") } main() console.log("C") CODE_BLOCK: async function main() { console.log("A") const value = await Promise.resolve(1) console.log("B") } main() console.log("C") CODE_BLOCK: A C B CODE_BLOCK: async function main() { const value = await getData() console.log(value) } CODE_BLOCK: async function main() { const value = await getData() console.log(value) } CODE_BLOCK: async function main() { const value = await getData() console.log(value) } COMMAND_BLOCK: getData().then(value => { console.log(value) }) COMMAND_BLOCK: getData().then(value => { console.log(value) }) COMMAND_BLOCK: getData().then(value => { console.log(value) }) CODE_BLOCK: async function main() { console.log("1") await Promise.resolve() console.log("2") } console.log("3") main() console.log("4") CODE_BLOCK: async function main() { console.log("1") await Promise.resolve() console.log("2") } console.log("3") main() console.log("4") CODE_BLOCK: async function main() { console.log("1") await Promise.resolve() console.log("2") } console.log("3") main() console.log("4") CODE_BLOCK: 3 1 4 2 CODE_BLOCK: 3 1 4 2 CODE_BLOCK: 3 1 4 2 CODE_BLOCK: console.log("A") async function test() { console.log("B") await Promise.resolve() console.log("C") } test() console.log("D") CODE_BLOCK: console.log("A") async function test() { console.log("B") await Promise.resolve() console.log("C") } test() console.log("D") CODE_BLOCK: console.log("A") async function test() { console.log("B") await Promise.resolve() console.log("C") } test() console.log("D") CODE_BLOCK: A B D C CODE_BLOCK: A B D C CODE_BLOCK: A B D C CODE_BLOCK: const a = await fetchA() const b = await fetchB() CODE_BLOCK: const a = await fetchA() const b = await fetchB() CODE_BLOCK: const a = await fetchA() const b = await fetchB() CODE_BLOCK: const [a, b] = await Promise.all([ fetchA(), fetchB() ]) CODE_BLOCK: const [a, b] = await Promise.all([ fetchA(), fetchB() ]) CODE_BLOCK: const [a, b] = await Promise.all([ fetchA(), fetchB() ]) CODE_BLOCK: const a = await fetchA() const b = await fetchB() CODE_BLOCK: const a = await fetchA() const b = await fetchB() CODE_BLOCK: const a = await fetchA() const b = await fetchB() CODE_BLOCK: const aPromise = fetchA() const bPromise = fetchB() const a = await aPromise const b = await bPromise CODE_BLOCK: const aPromise = fetchA() const bPromise = fetchB() const a = await aPromise const b = await bPromise CODE_BLOCK: const aPromise = fetchA() const bPromise = fetchB() const a = await aPromise const b = await bPromise - Why does Promise.then() run before setTimeout? - Why does await run “later” even though it looks synchronous? - What is actually happening behind async / await? - Microtask Queue - setTimeout is called - It is handed off to Web APIs - Timer starts outside the call stack - JavaScript continues execution - Run everything in the call stack - When empty → take one task from the task queue - Push it to the call stack - setTimeout registers callback - call stack becomes empty - event loop pushes task - setInterval - Promise.then - queueMicrotask - Run call stack - Run ALL microtasks - Run ONE task - Run microtasks again - fetchA runs - fetchB runs - Call Stack → current execution - Web APIs → timers, network - Task Queue → setTimeout - Microtask Queue → Promise - Event Loop → orchestrates everything - Promise → runs in microtask queue - setTimeout → runs in task queue - microtasks always run first - await → splits code into microtasks - Why Promise runs before setTimeout - Why await runs “later” - Why sequential vs parallel happens