DEV Community

Cover image for Understanding JavaScript Function Executions: Call Stack, Event Loop, Tasks & More
Ahmed Niazy
Ahmed Niazy

Posted on

Understanding JavaScript Function Executions: Call Stack, Event Loop, Tasks & More


JavaScript is often described using words like "single-threaded," "asynchronous," and "non-blocking" — but what do these really mean? In this article, we’ll break down how JavaScript executes functions and manages concurrency using the call stack, event loop, microtasks, macrotasks, and more. Whether you're a beginner or aiming to be a senior developer, understanding these mechanics is essential for writing efficient and predictable JavaScript code.

1. The Call Stack: Where Execution Happens

The call stack is a data structure that keeps track of function calls. When a function is called, it's added to the stack (pushed). When it finishes, it's removed (popped). JavaScript runs code top to bottom and uses the stack to manage function execution order.

function greet(name) {
  console.log("Hello, " + name);
}

greet("Alice");
Enter fullscreen mode Exit fullscreen mode

In this example, greet("Alice") is pushed to the stack. Once console.log runs, it is popped off. The stack ensures that only one function executes at a time.

2. Memory Heap

This is where JavaScript stores memory for variables, objects, and functions. It's an unstructured region and complements the call stack by holding references used during execution.

3. Web APIs and Asynchronous Behavior

JavaScript offloads certain operations to the browser or runtime environment (like timers, DOM events, and HTTP requests). These are handled by Web APIs, and once completed, they queue a callback to be executed later.

console.log("Start");
setTimeout(() => {
  console.log("Timeout finished");
}, 1000);
console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output:

Start
End
Timeout finished
Enter fullscreen mode Exit fullscreen mode

4. The Event Loop

The event loop constantly checks if the call stack is empty. If it is, it takes the next message from the task queue and pushes it onto the stack.

5. Tasks vs Microtasks

Macrotasks (Tasks):

Include things like:

  • setTimeout
  • setInterval
  • I/O operations
  • UI rendering

Microtasks:

Include:

  • Promises
  • queueMicrotask
  • MutationObserver

Microtasks are executed after the current function but before the next macrotask.

console.log("script start");

setTimeout(() => {
  console.log("setTimeout");
}, 0);

Promise.resolve().then(() => {
  console.log("promise1");
}).then(() => {
  console.log("promise2");
});

console.log("script end");
Enter fullscreen mode Exit fullscreen mode

Output:

script start
script end
promise1
promise2
setTimeout
Enter fullscreen mode Exit fullscreen mode

6. Promises and Async/Await

Promises represent a value that may be available now, or in the future. They're always asynchronous.

async function fetchData() {
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();
  console.log(data);
}

fetchData();
Enter fullscreen mode Exit fullscreen mode

await pauses execution in that function until the Promise resolves, but it doesn't block the entire thread.

7. Node.js Event Loop Phases

Node.js has a slightly more complex event loop with phases:

  1. Timers
  2. Pending callbacks
  3. Idle, prepare
  4. Poll
  5. Check (setImmediate)
  6. Close callbacks

Additionally, Node.js has process.nextTick, which runs before any microtasks.

process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("promise"));
setTimeout(() => console.log("timeout"), 0);
Enter fullscreen mode Exit fullscreen mode

Output:

nextTick
promise
timeout
Enter fullscreen mode Exit fullscreen mode

8. Common Mistakes and Best Practices

  • Avoid long-running synchronous code
  • Prefer async/await over deeply nested callbacks
  • Know when to use setTimeout vs Promise
  • Don’t abuse process.nextTick

9. Comparison with Other Languages

  • Java/Threads: Multi-threaded, needs synchronization
  • Python (asyncio): Similar event-loop concept
  • Go (goroutines): True concurrency

JavaScript avoids race conditions by running all code in a single thread and using event queues to manage async behavior.

10. Conclusion

JavaScript’s event-driven, single-threaded execution model is both powerful and efficient — once you understand it. Mastering the call stack, event loop, and task queues will improve your ability to write responsive and scalable JavaScript applications.


Happy coding!
Download Book

Top comments (0)