JavaScript ha un solo thread: esegue una cosa alla volta. Allora come fa a gestire timer, click e richieste di rete senza congelarsi? Grazie all'event loop.

console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// Output: 1, 4, 3, 2

Il codice sincrono gira per primo (1, 4). Poi l'event loop svuota la microtask queue (le Promise, quindi 3) e solo dopo la macrotask queue (i timer, quindi 2).

Ecco perché un setTimeout(fn, 0) non è davvero immediato: si mette in coda. E perché un ciclo pesante e sincrono blocca l'intera pagina: finché non finisce, l'event loop non gira. Spezza i lavori lunghi, o passali a un Web Worker.