O que é o Event Loop? Call Stack, Task Queue e Microtasks no JavaScript

description:Uma aula sobre o modelo de concorrência do JavaScript. Explico o funcionamento estrutural da Call Stack, Task Queue, Microtask Queue e Event Loop, apresento exemplos práticos do fluxo de execução com setTimeout e Promise.resolve, falo sobre prioridade e o temido bloqueio do Event Loop.
tags:javascript, nodejs, web, typescript, event-loop, desenvolvimento-web, microtask, macrotask, queue, jobqueue, promisequeue
author:Álvaro Neto
created:18 March 2026
updated:21 March 2026

Call Stack

A Call Stack é um estrutura que guarda a ordem de execução das funções. Isso garante que o JavaScript saiba onde começar ou continuar após a execução das funções.

Sempre que uma função é executada, um frame é criado e adicionado ao topo da call-stack.

Um frame contém todas as informações daquela função, variáveis locais e argumentos.

Execução da call stack

function c() {
  console.log('c')
}

function b() {
  console.log('bc')
  c()
  console.log('cb')
}

function a() { b() }

a();

A call stack será preenchida e esvaziada conforme a execução do programa:

> a é colocado na pilha
> b é colocado na pilha
> b logs 'bc'
> c é colocado na pilha
> c logs 'c'
> c é removido da pilha
> b logs 'cb'
> b é removido da pilha
> a é removido da pilha

Então durante a execução da função c(), o JavaScript sabe todas as funções anteriores que levaram a c(). Quando a função c() termina, ela é removida da call stack, e o processo repete para b().

Esse comportamento é o que define a call stack como uma pilha.

Task Queue

A task-queue (event-loop, queue, macrotask-queue) é uma estrutura que guarda os callbacks que vão ser executados no futuro (enquanto a Call Stack armazena as funções que estão sendo executadas no momento).

O objetivo da task-queue é possibilitar a execução de tarefas demoradas em background sem bloquear a execução de outras tarefas. Isso é necessário porque o JavaScript é single-threaded, executando somente uma tarefa por vez.

function f() { console.log('f') }
function e() { setTimeout(f, 0) }
function d() { console.log('d') }
function c() { console.log('c') }
function b() { console.log('b') }
function a() { console.log('a') }

a();
setTimeout(e, 100);
setTimeout(c, 100);
setTimeout(b, 0);
d();

A task-queue e a Call Stack trabalham junto conforme a execução do programa:

a => alocado na call-stack
a => logs 'a'
a => removido da call-stack

e => agenda uma alocação na task-queue em 100ms
c => agenda uma alocação na task-queue em 100ms 
b => alocado na task-queue (0ms)

d => alocado na call-stack
d => logs 'd'
d => removido da call-stack

// A execução chegou ao fim, a call-stack está vazia.
// No browser, esse é o momento de rerender (repaint, reflow)

b => transferida da task-queue para a call-stack
b => logs 'b'
b => removido da call-stack

// call-stack vazia, rerender.
// espera 100ms

e => alocado na task-queue
e => transferida da task-queue para a call-stack
e => removido da call-stack

// call-stack vazia, rerender

f => alocado na task-queue (0 ms) // pela execução de e() 
f => transferida da task-queue para a call-stack
f => logs 'f'
f => removido da call-stack

// call-stack vazia, rerender

c => alocado na task-queue
c => transferida da task-queue para a call-stack
c => logs 'c'
c => removido da call-stack

Note que a função f() foi alocada antes que a função c(), mesmo o timeout sendo 100ms para ambas c() e e(). Isso acontece pois o setTimeout(fn, 0) força uma alocação na task-queue imediata, mesmo que o tempo de outros timeouts já tenha passado.

Microtask Queue

A microtask-queue (job-queue, queue, promise-queue) é uma estrutura semelhante a Task Queue, adicionada na ECMAScript 2015, que guarda tasks criadas a partir de Promises (Promise.then, catch, finally) e queueMicrotask. Essas tasks serão executadas no futuro, com prioridade.[^1]

As microtasks tem prioridade de execução em relação as tasks: O agente prioriza mover microtasks para Call Stack, e após isso, enquanto a microtask-queue não estiver vazia, nenhuma outra ação é executada (nem mesmo re-renderizar).

function ab() {
  console.log('ab');
  
  return Promise.resolve()
}


function a() {
    setTimeout(e, 0)
    console.log('a')  
}
  
function b() {
  console.log('b')
}

function c() {
  console.log('c')
}

function e() {
  console.log('e')
}


ab().then(a).then(b)
setTimeout(c, 0)

Agora, a Microtask Queue (Ou promise Queue), a Task Queue (Ou só Queue) e a Call Stack trabalham juntas na execução:

ab => alocado na call-stack
ab => logs 'ab'
a => alocado na microtask-queue
ab => removido da call-stack
c => alocado na task-queue

// Aqui a call-stack ficou vazia, contudo, como uma
// microtask tem prioridade em relação ao rerender
// ela foi transferida primeiro.

a => transferida da microtask-queue para a call-stack
e => alocado na task-queue
a => logs 'a'
a => removido da call-stack
b => alocado na microtask-queue

// novamente call-stack vazia, mas microtasks tem prioridade

b => transferido da microtask-queue para a call-stack
b => logs 'b'
b => removido da call-stack

// A microtask-queue está vazia e a call-stack também.
// No browser, esse é o momento de rerender (repaint, reflow)

c => transferida da task-queue para a call-stack
c => logs 'c'
c => removido da call-stack

// call-stack vazia, rerender

e => transferida da task-queue para a call-stack
e => logs 'e'
e => removido da call-stack

// call-stack vazia, rerender

Não existe limite de queues que o browser/node.js podem ter. No navegador, existe pelo menos uma DOM queue e uma Timer Queue, por exemplo. No entanto, só pode existir somente uma microtask-queue (de acordo com a spec), todas as outras queues serão do tipo task-queue.

Event Loop

O termo event-loop se refere ao mecanismo completo de funcionamento das Tasks Queue, da Microtask Queue, da Call Stack e do algoritmo de looping que processa as tasks.

while (Scheduler.waitForTask()) {  
  const taskQueue = Scheduler.selectTaskQueue();  

  if (taskQueue.hasNextTask()) {  
    taskQueue.processNextTask();  
  }  
  
  const microtaskQueue = Scheduler.microTaskQueue;  

  while (microtaskQueue.hasNextMicrotask()) {  
	microtaskQueue.processNextMicrotask();  
  }  
 
  rerender(); // only in browser
}

O que significa "acesso exclusivo"?

Cada task é executada por completo antes que outra task comece a ser executada. Então duas tasks nunca serão executadas ao mesmo tempo.

let i;

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

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

O que acontece é:

i++ => console.log(i) => i++ => console.log(i)

E nunca:

i++ => i++ => console.log(i) => console.log(i)

Congelar a Event-Loop

Por causa da execução exclusiva, se uma task demorar indefinidamente, outras tasks nunca serão executadas e o programa vai travar.

Contudo, o browser fica de olho no tempo de execução das tasks e pergunta ao usuário se ele quer abortar tasks demoradas demais.

Obrigado por ler!!!

Espero que esse artigo tenha ajudado você a entender melhor o que é o Event Loop. Se houver algum comentário, notou algum erro, ou quer falar comigo, entre em contato pelas minhas redes sociais ou e-mail no card ao lado ou embaixo.