Overview

1 When a hello isn’t hello

JavaScript feels approachable because you can write a few lines and see results immediately, but that ease masks a lot of hidden machinery. This chapter opens by arguing that real expertise requires understanding what actually happens when code runs: how engines compile and execute, how host runtimes schedule work, and where the boundaries lie between language rules, engine strategies, and runtime behavior. The book positions itself as an owner’s manual for those internals, so developers can diagnose performance issues and subtle bugs rather than just “drive” the language.

To illustrate the point, the chapter presents a small “Hello” puzzle that mixes queueMicrotask, process.nextTick, setTimeout, a resolved Promise, and setImmediate. Although the code is the same, Node.js in CommonJS prints “Hello,” while Node.js in ESM, Deno, and Bun each produce different orders, and browsers would vary further. The discrepancies trace back to host-defined scheduling: distinct event loops, phases, and multiple task queues (microtasks, nextTicks, immediates) drained in different orders. The lesson is clear: avoid relying on precise asynchronous timing or queue ordering unless you know the specific runtime’s rules.

The chapter then maps out the ecosystem’s moving parts. ECMAScript defines the language (for example, how promises and the microtask queue behave), engines like V8, SpiderMonkey, and JavaScriptCore implement it with their own optimizations, and host runtimes (Node.js, Deno, Bun, browsers) embed those engines and control scheduling, timers, I/O, and available APIs. Some behavior is language-defined (should be consistent everywhere), some implementation-defined (tends to align across runtimes sharing an engine), and some host-defined (can legitimately differ). With Node.js v24 as a baseline, the book will unpack types and coercion quirks, promises and queues, streams, cryptography, and modules—equipping readers to reason about both the language and the runtime realities that make “hello” not always “hello.”

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 does the “Hello” puzzle in Chapter 1 illustrate?That identical JavaScript can produce different outputs depending on the host runtime, its version, and even the module system. It exposes how scheduling, task queues, and event-loop behavior vary across environments, so “Hello” isn’t always “Hello.”
Why does the CommonJS version in Node.js print “Hello” in that order?Because Node.js schedules and drains its queues in a specific order: it runs all nextTicks first (process.nextTick → prints H), then microtasks (queueMicrotask and Promise.then → prints e and l), then enters the event loop and handles timers (setTimeout → prints l), and finally immediates (setImmediate → prints o and a newline). Together that yields “Hello”.
How do microtasks, nextTicks, and immediates differ, and which run first?Microtasks are language-defined (used by Promise.then and queueMicrotask). process.nextTick and setImmediate are Node.js host-defined queues. In Node.js, nextTicks run before microtasks; browsers don’t have nextTicks at all. Immediates run in their own phase after timers and I/O checks.
What is the event loop, and why does it vary across runtimes?The event loop schedules when JavaScript runs in response to events. Its design differs by host: browsers optimize for a responsive UI, while Node.js optimizes for OS-level I/O and divides the loop into phases (timers, pending callbacks, poll, check/immediates, etc.). These differences change observable execution order.
Why does changing a file from .js (CommonJS) to .mjs (ESM) in Node.js change the output?ESM bootstraps and schedules work differently than CommonJS in Node.js, affecting when queues are drained and when the event loop starts. That shifts the relative ordering of nextTicks, microtasks, timers, and immediates—so identical code with only a different extension can print a different sequence.
Why do the same lines of code behave differently in Node.js, Deno, and Bun?Because host-defined APIs and scheduling differ. For example, setImmediate is not a web standard; in Deno it isn’t a global by default and must be imported from node:timers. Engines also differ (e.g., V8 vs JavaScriptCore), and each runtime has distinct goals and event-loop policies, so ordering varies.
What does “scheduling” mean in this context?Scheduling is the host runtime deciding when to run JavaScript in response to events. Code doesn’t run automatically; events (like a click, timer, or I/O) enqueue tasks, and the runtime runs them one at a time to completion according to its queue and phase rules.
Are microtasks part of the event loop?Not directly. The microtask queue is separate from the event loop. Each host decides when to drain it—and may do so many times within a single loop turn. This flexibility is why promise timing can vary by environment.
Should I rely on precise async ordering across environments?Generally, no. Depending on exact ordering across queues (microtasks, nextTicks, timers, immediates) is fragile and environment-specific. Even within Node.js, switching from CommonJS to ESM can change results. Prefer designs that don’t assume a particular drain order.
What tools and versions does the book use, and where are the examples?The baseline is Node.js 24.0.0 or higher, with comparisons in Deno, Bun, browsers, and Cloudflare Workers as needed. All examples (including the “Hello” puzzle) are available at https://github.com/jasnell/how-javascript-things-work, and install instructions are in the Appendix.

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
$499.99
only $41.67 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
$499.99
only $41.67 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