Overview

1 When a hello isn’t hello

JavaScript feels simple at first glance—no compiling, types, or memory management—yet that simplicity masks deep complexity. This chapter sets the stage for thinking beyond surface syntax to how the language actually runs in real environments. It introduces the book’s mission: to illuminate the inner workings of JavaScript engines and host runtimes so developers can reason about correctness and performance across browsers and server-side platforms.

A small asynchronous “Hello” puzzle demonstrates why identical code can behave differently across environments and even within the same runtime under different module systems. Node.js CommonJS, Node.js ESM, Deno, and Bun each schedule microtasks, nextTicks, timers, and immediates in different orders because their event loops and task queues are host-defined. Subtle factors like runtime version or file extension can change execution order, while standardized features (like promises) and familiar quirks (like floating-point arithmetic or coercion rules) add their own surprises. The key lesson is to avoid depending on precise timing between queues and to recognize how much behavior is shaped by runtime specifics.

To reason about these outcomes, the chapter distinguishes the roles of the ECMAScript specification (language-defined behavior), JavaScript engines (implementation strategies such as V8, JavaScriptCore, SpiderMonkey), and host runtimes (environment-defined scheduling, I/O, and available APIs). It surveys how event loops differ between browsers and Node.js and explains why multiple queues exist and are drained differently. With Node.js v24 as the baseline but comparisons across other runtimes, the book positions itself as an owner’s manual: a practical guide to diagnosing and improving real systems, leading into deeper explorations of types, promises, streams, cryptography, and modules.

An illustration of the phases of the Node.js Event Loop from the project documentation (https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick). At the start of each iteration Node.js will first execute timers, then move on to calling pending callbacks, preparing for I/O, polling the operating system for I/O, then performing various checks and cleanup operations before starting over again at the top with timers.

Summary

  • Identical JavaScript code can yield different results in Node.js, Deno, and Bun due to differences in runtime implementation choices
  • The event loop controls execution order, with each runtime implementing different scheduling priorities
  • Language specifications define what must be consistent; implementations and hosts define what can vary
  • Understanding the distinction between language, implementation, and host behaviors prevents unexpected bugs

FAQ

What’s the core lesson of “When a hello isn’t hello”?The same JavaScript can behave differently across runtimes and even within the same runtime when you change the module system. Those differences largely come from host runtime scheduling choices (event loop phases and task queues), not from the ECMAScript language itself.
How does Node.js CommonJS schedule the puzzle’s tasks to print “Hello”?In Node.js (CommonJS), process.nextTick() callbacks run before microtasks. After nextTicks, microtasks (queueMicrotask and Promise reactions) run, then timers (setTimeout(…)) and finally immediates (setImmediate(…)). That ordering produces H, then e and l, then the timeout’s l, and finally o, yielding “Hello”.
Why does switching the file to .mjs (ESM) change the output in Node.js?ESM changes how Node.js boots and when it drains various queues during startup and teardown. That altered scheduling—especially around when microtasks are flushed relative to timers and immediates—leads to a different, split output even though the code is otherwise identical.
Why do Deno and Bun produce different results for the same code?They are different host runtimes with different event loop implementations and queue-draining policies. Some APIs are Node-specific (for example, setImmediate and process.nextTick), so Deno requires an explicit import for setImmediate and may schedule tasks differently; Bun uses JavaScriptCore and has its own scheduling nuances.
What are microtasks, nextTicks, and immediates?Microtasks are a language-level queue used by promises and queueMicrotask, drained at host-chosen points. nextTick and setImmediate are Node.js host-defined queues: nextTick runs before microtasks in Node.js, while immediates run in the “check” phase after timers. Browsers do not have process.nextTick.
Is the microtask queue part of the event loop?No. The microtask queue is specified by the language and is separate from the event loop’s macro-task processing. Each host decides when to drain microtasks and may do so multiple times within a single turn of the event loop, which is why ordering can vary between environments.
What is the event loop and why does it vary between browsers and Node.js?The event loop pulls scheduled work and runs it one task at a time, giving JavaScript its single-threaded execution model per context. Browsers prioritize UI responsiveness and animation cadence, while Node.js optimizes for I/O and process lifetime, so their loop phases and timing choices differ.
What’s the difference between language-, implementation-, and host-defined behavior?Language-defined behavior (like promises) must be consistent across environments. Implementation-defined covers engine-specific choices (e.g., V8 vs JavaScriptCore internals), while host-defined behavior (like process.nextTick and setImmediate in Node.js) may vary widely between runtimes.
Should I rely on specific task ordering across runtimes? How can I avoid pitfalls?Avoid relying on precise ordering between queues (nextTick, microtasks, timers, immediates) across environments. Prefer explicit sequencing with async/await or promise chaining, minimize use of host-specific timing APIs unless necessary, and test across the runtimes and versions you support.
What do I need to follow along with the chapter’s examples?Use Node.js 24 or newer as the baseline. Some examples also run in Deno, Bun, browsers, or Cloudflare Workers; adjust for host-specific APIs (e.g., import setImmediate in Deno). The chapter links to a GitHub repo with all sample code: https://github.com/jasnell/how-javascript-things-work.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$399.99
only $33.33 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • JavaScript in Depth ebook for free
choose your plan

team

monthly
annual
$49.99
$399.99
only $33.33 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • JavaScript in Depth ebook for free