Get AI summaries of any video or article — Sign up free
JavaScript: How It's Made thumbnail

JavaScript: How It's Made

Fireship·
5 min read

Based on Fireship's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

JavaScript’s main thread can run only one synchronous computation at a time, so long-running loops freeze responsiveness.

Briefing

JavaScript’s “magic” is mostly a set of engineering tradeoffs: it runs on a single main thread, manages memory with garbage collection, and stays responsive by using an event loop that schedules work instead of blocking. That combination explains why an infinite loop can freeze a browser tab, why long tasks don’t necessarily lock up the page, and why promise callbacks can run before timer callbacks—even when both are scheduled for “0” delay.

At the hardware level, running JavaScript still means allocating memory in RAM for runtime data—variables, objects, and other structures—and executing instructions on the CPU. The language’s “high-level” nature is about abstraction: machine code is direct but impractical for building websites; assembly is tied to specific CPUs/OSes; C adds portability but still forces developers to think about low-level concerns like memory. JavaScript and Python push those concerns upward using features like garbage collection and dynamic typing, letting developers write without explicit type annotations or manual memory management.

From there, translation and execution mechanics matter. Code can be interpreted or compiled. Traditional interpreters translate and execute instructions immediately, line by line, while compilers analyze ahead of time and produce a binary. JavaScript historically leaned toward interpretation, but modern engines use just-in-time (JIT) compilation to improve performance. Two major implementations—Mozilla’s SpiderMonkey and Google’s V8—use JIT differently, but both aim to turn frequently used JavaScript into native machine code rather than repeatedly interpreting bytecode.

Several other defining traits shape how programs behave. JavaScript is dynamically typed, meaning types aren’t declared in the code; they’re tied to runtime values. It’s multi-paradigm, supporting multiple programming styles. It’s also prototype-based: objects link to prototypes through a chain, enabling inheritance-like behavior without class-based syntax.

The responsiveness story hinges on concurrency. JavaScript is single-threaded, so only one computation can run at a time. A never-ending while loop demonstrates the failure mode: the main thread stays busy, the browser can’t process new events, and the UI becomes unresponsive. Under the hood, execution uses two key memory regions: the call stack and the heap. The call stack is a fast, structured area where function frames are pushed and popped as calls return; runaway recursion eventually triggers “call stack size exceeded.” The heap is a less structured memory pool for objects and closure-captured values, and it’s garbage collected when references disappear.

To handle long-running work without blocking, JavaScript relies on the event loop. The runtime processes synchronous code to completion, then checks a queue for pending callbacks (such as user clicks). Asynchronous operations like HTTP requests or file-system interactions can run in separate browser thread pools, and their results are delivered back as queued tasks. Promises add another layer: promise callbacks go to a microtask queue with higher priority than the main task queue used by timers and DOM events. That ordering is why a promise scheduled after a setTimeout(0) can still run first. In short, JavaScript stays responsive by scheduling work across queues and by treating the main thread as a place to run short, synchronous chunks rather than to wait.

Cornell Notes

JavaScript runs on a single main thread, so any long-running synchronous code (like an infinite while loop) blocks user interaction. Execution uses a call stack for function frames and a heap for objects and closure-captured values; the heap is garbage collected when references disappear. Modern JavaScript engines (SpiderMonkey and V8) use just-in-time compilation to improve performance, even though JavaScript is often described as interpreted. The event loop keeps the program responsive by running synchronous code to completion, then pulling callbacks from a queue. Promises add a microtask queue that runs before the main task queue, so promise callbacks can fire before setTimeout callbacks even with “0” delay.

What does “single-threaded” mean in practice for JavaScript running in a browser?

Only one piece of JavaScript can execute at a time on the main thread. If code enters an infinite while loop, the thread never returns to the runtime’s event-processing logic, so the browser can’t dispatch UI events like clicks. The tab typically spikes CPU usage (near a full core) until the process is terminated or the page is refreshed.

How do the call stack and heap differ, and how does each fail?

The call stack is a high-performance, structured region where each function call pushes a frame containing local variables; returning pops frames off. Recursive calls that never reach a return eventually trigger “call stack size exceeded.” The heap is a mostly unstructured memory pool for objects and values referenced across multiple calls (including closure-captured data). Garbage collection clears heap memory when objects become unreachable, so developers don’t manually free memory like in C.

Why do modern engines like SpiderMonkey and V8 matter if JavaScript is “interpreted”?

JavaScript engines use just-in-time (JIT) compilation to boost performance. Instead of only translating and executing line-by-line, they compile frequently used code paths down to native machine code. SpiderMonkey and V8 both use JIT, but they implement the strategy differently; the key point is faster execution without changing how developers write typical JavaScript.

How does the event loop prevent blocking while still using a single thread?

The runtime runs the current synchronous script to completion, then checks for queued callbacks (events like button clicks). Asynchronous operations (HTTP requests, file-system work) can run in separate browser thread pools, and when they finish, their results are scheduled back as tasks. The main thread only processes callbacks when it’s free, so it doesn’t wait idly for long operations.

Why can a Promise callback run before a setTimeout(0) callback?

Promises use a microtask queue, which has higher priority than the main task queue used for timers and many DOM callbacks. During each event-loop iteration, the runtime processes synchronous code first, then drains the microtask queue, and only afterward runs callbacks from the main task queue. That ordering lets promise handlers run before timer handlers even when both are scheduled with zero delay.

Review Questions

  1. How would an infinite while loop affect event handling, and what does that reveal about JavaScript’s concurrency model?
  2. Describe the lifecycle of function calls using the call stack, and explain what triggers a call stack size exceeded error.
  3. In what order does the event loop process synchronous code, microtasks, and timer/DOM tasks, and how does that ordering affect Promise vs setTimeout(0)?

Key Points

  1. 1

    JavaScript’s main thread can run only one synchronous computation at a time, so long-running loops freeze responsiveness.

  2. 2

    The call stack stores function frames and local variables; recursion without termination eventually triggers a call stack size exceeded error.

  3. 3

    The heap stores objects and closure-captured values and is garbage collected when references become unreachable.

  4. 4

    Modern JavaScript engines (SpiderMonkey and V8) use just-in-time compilation to convert hot code into native machine code for speed.

  5. 5

    The event loop keeps the program responsive by running synchronous code to completion and then processing queued callbacks.

  6. 6

    Promises schedule work in a microtask queue that runs before the main task queue used by setTimeout and many DOM events.

Highlights

An infinite while loop prevents the browser from handling new events because the single JavaScript thread never returns to the event loop.
The heap is garbage collected, while the call stack is tightly structured—one fails via stack overflow, the other via unreachable references.
JIT compilation in SpiderMonkey and V8 improves performance without requiring developers to change how they write JavaScript.
The microtask queue gives Promises priority over setTimeout(0), so promise handlers can run first.
The event loop is the mechanism that turns asynchronous operations into queued callbacks processed later.

Topics

  • JavaScript Execution Model
  • JIT Compilation
  • Call Stack and Heap
  • Event Loop
  • Microtask Queue

Mentioned

  • SpiderMonkey
  • V8