Micro and Macro tasks in JavaScript

Michael Pautov
3 min readJun 29, 2020


Multithreading is what made great computing speeds possible. Today, it is a compulsory feature in all operating systems and programming languages. While even the old languages like C++ added built-in thread support in the somewhat latest updates, JavaScript remained stubborn.

But multithreading was needed in browsers nonetheless, so the concept of event loop is used to perform asynchronous functions in JavaScript. Event loop monitors message queue and the execution stack to make sure callback functions are executed as soon as the execution stack is empty. The callback functions are further divided into micro-tasks and macro-tasks.

We will look at the difference and need for micro and macro tasks in JavaScript in this article. Let’s start with the supporting concepts.

Execution Stack

The environment in which a JavaScript code element runs in is called an execution context. It stores all the local variables for the code block and evaluation criteria. Every function receives a new execution context as soon as it is called. These execution contexts are stored on top of each other in a container like object called the execution stack.

Message Queue

When a callback function is called in an existing execution context, it is pushed to the message queue. They remain in the queue until the execution stack is empty which is when the event loop finally executes these tasks. Reading the code, Queueing up the tasks, and execution of the tasks is done by the event loop.

This message queue is what actually makes these tasks asynchronous and thus helps JavaScript programmers implement multithreading. But as it is seen, all tasks in the message queue are not created and treated equally. They are further divided into two queues; micro-task queue and macro-task queue.

Micro-tasks and Macro-tasks

Simply speaking, micro-tasks are used to create a queue of synchronous tasks inside the message queue which is executed asynchronously with the execution stack.

How are micro and macro tasks executed?

An event loop has a task queue which is the macro-task queue. Once it starts executing the callback functions in the message queue, it takes one macro-task, executes it and goes to execute the micro-task queue. It keeps executing the micro-task queue until it is finished. Then it goes back to the macro-tasks (task) queue and executes another one.

It might happen that each micro-task keep queuing up more micro-tasks and we are stuck in an infinite loop. To avoid this, JavaScript limits the number of micro-tasks that can be executed simultaneously to 1000. After 1000 micro-tasks, it has to pick up a macro-task and then come back to the micro-task queue.

Render is also blocked while the micro-tasks are executing to keep the UI intact for the user. So if we are stuck in the micro-task queue for too long, the page might become unresponsive.

How to divide micro and macro tasks?

It is advised to the programmers to use micro-tasks when you want to perform some functions synchronously in an asynchronous manner. Otherwise, it is best to use macro-tasks.

Few examples of macro-tasks are setTimeout, setInterval, setImmediate, I/O tasks, UI rendering. While promises are micro-tasks as they are executed as soon as their constructor is invoked.

Wrapping up

We discussed the differences, merits, demerits, and examples of micro and macro tasks in JavaScript. We also touched a bit upon how they are used to implement multi-threading in browsers using JS. Hope this clears any confusion that you might have had regarding the topic. If you’ve got any more questions, feel free to ask in the comments below.