Ever clicked a button and your entire app just froze? You're not alone.
Let’s go back to the basics and unpack what’s really happening under the hood.
One thread to rule them all
In the browser, your JavaScript runs in a single thread by default. That means everything from rendering the UI to handling user input to executing your code happens in one sequence. If you block this thread, nothing else happens.
while (true) { // infinite loop — UI becomes unresponsive }
Even animations, clicks, scrolls — they all wait for your code to finish.
Processes vs threads
A process is like a separate app: it has its own memory and runs independently. A thread is a unit of execution inside that process, sharing memory with others.
Modern browsers isolate each tab in its own process for security and stability. But inside each tab, your JS runs in one main thread.
Want more threads? You have to create them manually using Web Workers.
const worker = new Worker("heavy-task.js")
Web Workers don’t share memory. You communicate via messages (postMessage
/ onmessage
). It’s like sending letters — safe, but a bit inconvenient.
Async is not parallel
Just because you write async
doesn’t mean the work happens in parallel. JS uses asynchronous callbacks, not native concurrency (unless you go out of your way).
fetch('/data').then(() => { console.log('Done') }) console.log('This runs first')
Here’s what really happens:
-
fetch()
is handed off to a Web API. - Your thread keeps going — no waiting.
- When the result is ready, it’s put in a callback queue.
- The event loop picks it up when the thread is free.
Enter the event loop
The event loop is a behind-the-scenes mechanism that monitors two things:
- the call stack (what's running now)
- the callback queue (what’s waiting to run)
If the stack is empty, it picks something from the queue and runs it.
So if you freeze the stack with heavy code, nothing from the queue gets executed.
So why does your app freeze?
Because you didn’t give the event loop a chance to breathe.
Heavy loops, complex calculations, sync XHR, or just too much logic in one go — they all block the thread.
const now = performance.now() while (performance.now() - now < 2000) {} alert("UI frozen for 2s")
You can prevent this by:
- Breaking work into smaller chunks
- Using
setTimeout(fn, 0)
orrequestIdleCallback
- Offloading to Web Workers
Bonus curiosity: DOM isn’t thread-safe
Ever wondered why Web Workers can’t touch the DOM?
Because the DOM API isn’t designed for concurrency. It’s not thread-safe, meaning two threads accessing it could break things. That’s why rendering is tied to the main thread.
The basics you skipped
If you understand that:
- JS is single-threaded
- Async just delays work, it doesn’t parallelize it
- The event loop needs space to operate
...then you stop writing code that accidentally sabotages your own UI.
Next time: Search algorithms and data structures: understand the B-Tree
Top comments (0)