DEV Community

Cover image for Week 4 - You're not stuck, you just skipped the basics: Threads, processes and why your app “freezes”
Adam Neves
Adam Neves

Posted on

Week 4 - You're not stuck, you just skipped the basics: Threads, processes and why your app “freezes”

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 } 
Enter fullscreen mode Exit fullscreen mode

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") 
Enter fullscreen mode Exit fullscreen mode

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') 
Enter fullscreen mode Exit fullscreen mode

Here’s what really happens:

  1. fetch() is handed off to a Web API.
  2. Your thread keeps going — no waiting.
  3. When the result is ready, it’s put in a callback queue.
  4. 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") 
Enter fullscreen mode Exit fullscreen mode

You can prevent this by:

  • Breaking work into smaller chunks
  • Using setTimeout(fn, 0) or requestIdleCallback
  • 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)