"JavaScript é single-thread" e ao mesmo tempo "JavaScript não trava ao baixar dados". Como as duas coisas são verdade? A resposta é o event loop, talvez o conceito que mais separa o júnior do pleno — porque explica de uma vez a ordem de execução, o async e por que sua UI continua respondendo.
Single-thread, mas não-bloqueante
O JS executa uma instrução por vez, numa única thread. Se fosse só isso, baixar um arquivo de 3 segundos congelaria a página inteira. O truque é que o JS não faz esse trabalho pesado sozinho: ele delega para o ambiente (o navegador) e segue em frente. Quando o trabalho termina, o resultado volta para a fila.
A call stack
A call stack é a pilha de funções em execução. Chamou uma função, empilhou; ela retornou, desempilhou. É aqui que o "single-thread" vive: só existe uma pilha, e ela processa uma coisa de cada vez.
Web APIs: o trabalho terceirizado
Quando você chama setTimeout, fetch ou escuta um clique, quem cuida disso são as Web APIs do navegador — fora da thread do JS. Pense num restaurante: o garçom (a thread) anota o pedido e entrega na cozinha (Web API) em vez de ficar parado cozinhando. Ele volta a atender outras mesas.
Duas filas: macrotasks e microtasks
Quando a Web API termina, o callback não roda na hora — ele entra numa fila. E existem duas:
- Task queue (macrotasks):
setTimeout, eventos de I/O, etc. - Microtask queue:
.thende Promises,queueMicrotask.
A regra crucial: as microtasks têm prioridade. Após cada tarefa, o event loop esvazia toda a fila de microtasks antes de pegar a próxima macrotask.
O exemplo clássico de ordem
Esse trecho é praticamente um clássico de entrevista — saber prever a saída prova que você entende o loop.
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Saída: 1, 4, 3, 2
Por quê? 1 e 4 são síncronos (rodam direto na stack). O setTimeout vira macrotask; o .then vira microtask. Quando a stack esvazia, o loop roda todas as microtasks (3) antes de tocar na macrotask (2).
O loop, em diagrama
┌──────────────┐
│ Call Stack │◀── roda código síncrono
└──────┬───────┘
│ delega
▼
┌──────────────┐
│ Web APIs │ (timer, fetch...)
└──────┬───────┘
│ pronto → enfileira
┌──────▼────────────────────┐
│ Microtasks (.then) ◀ alta prioridade
│ Macrotasks (setTimeout) │
└───────────────────────────┘
Event Loop: stack vazia? puxa da fila
| Microtask | Macrotask | |
|---|---|---|
| Exemplos | Promise.then | setTimeout, eventos |
| Prioridade | Alta (esvazia tudo) | Uma por ciclo |
O event loop não acelera nada — ele apenas garante que a thread nunca fique parada esperando, mantendo a UI viva enquanto o trabalho pesado acontece em paralelo.
No dia a dia isso aparece quando você precisa entender por que um await "espera" sem travar a tela, ou por que um loop pesado e síncrono realmente congela tudo — ele nunca solta a stack para o loop respirar.
Conclusão
A stack roda o código síncrono, as Web APIs cuidam do assíncrono, e o event loop reabastece a stack das filas — microtasks primeiro. Entender essa coreografia é o que permite escrever código async sem surpresas. Quando o async já é trivial, o próximo desafio é gerenciar o estado dos componentes.
"JavaScript is single-threaded" and at the same time "JavaScript doesn't freeze while downloading data". How can both be true? The answer is the event loop, perhaps the concept that most separates junior from mid-level — because it explains execution order, async and why your UI keeps responding all at once.
Single-threaded, but non-blocking
JS executes one instruction at a time, on a single thread. If that were all, downloading a file that takes 3 seconds would freeze the entire page. The trick is that JS doesn't do that heavy work itself: it delegates to the environment (the browser) and moves on. When the work finishes, the result comes back via a queue.
The call stack
The call stack is the stack of running functions. Call a function, it gets pushed; it returns, it gets popped. This is where "single-threaded" lives: there is only one stack, and it processes one thing at a time.
Web APIs: outsourced work
When you call setTimeout, fetch or listen for a click, the browser's Web APIs take care of it — outside the JS thread. Think of a restaurant: the waiter (the thread) takes the order and hands it to the kitchen (Web API) instead of standing around cooking. He goes back to serving other tables.
Two queues: macrotasks and microtasks
When the Web API finishes, the callback doesn't run right away — it goes into a queue. And there are two:
- Task queue (macrotasks):
setTimeout, I/O events, etc. - Microtask queue: Promise
.then,queueMicrotask.
The crucial rule: microtasks have priority. After each task, the event loop drains the entire microtask queue before picking the next macrotask.
The classic ordering example
This snippet is practically an interview classic — being able to predict the output proves you understand the loop.
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2
Why? 1 and 4 are synchronous (they run directly on the stack). The setTimeout becomes a macrotask; the .then becomes a microtask. When the stack empties, the loop runs all microtasks (3) before touching the macrotask (2).
The loop, in a diagram
┌──────────────┐
│ Call Stack │◀── runs synchronous code
└──────┬───────┘
│ delegates
▼
┌──────────────┐
│ Web APIs │ (timer, fetch...)
└──────┬───────┘
│ done → enqueues
┌──────▼────────────────────┐
│ Microtasks (.then) ◀ high priority
│ Macrotasks (setTimeout) │
└───────────────────────────┘
Event Loop: stack empty? pull from queue
| Microtask | Macrotask | |
|---|---|---|
| Examples | Promise.then | setTimeout, events |
| Priority | High (drains everything) | One per cycle |
The event loop doesn't speed anything up — it just ensures the thread never sits idle waiting, keeping the UI alive while the heavy work happens in parallel.
Day to day this shows up when you need to understand why an await "waits" without freezing the screen, or why a heavy synchronous loop really freezes everything — it never releases the stack to let the loop breathe.
Conclusion
The stack runs synchronous code, Web APIs handle the asynchronous work, and the event loop refills the stack from the queues — microtasks first. Understanding this choreography is what lets you write async code without surprises. Once async is trivial, the next challenge is managing component state.