Library · Summary & review

Eloquent JavaScript

By Marijn Haverbeke. Free online, updated for ES2024, and probably the most honest JavaScript book ever written.

FR EN
Eloquent JavaScript book cover, Marijn Haverbeke

Eloquent JavaScript

Eloquent JavaScript: A Modern Introduction to Programming

8.8 /10

« Free, up to date, honest — there's simply no excuse not to read it. »

  • AuthorMarijn Haverbeke
  • Edition4e éd. 2024 · 456 pages
  • PublisherNo Starch Press
  • Onlineeloquentjavascript.net · free
  • This page~9 min read
Book rating across 5 dimensionsIdeas9/10Practical9/10Readability9/10Aged well9/10Examples8/10

The most honest JavaScript introduction, freely available online. Updated for ES2024.

Why this book

Eloquent JavaScript has been online for free since 2011. Haverbeke updates it with every major ECMAScript revision, no exception: the fourth edition covers ES2024. Most JavaScript books age in a drawer; this one has been a living document for fifteen years.

But what makes it different isn't the price or the update cadence. It's the tone. Haverbeke opens the introduction by admitting he quickly came to despise JavaScript the first time he used it. Then he explains, a few pages later, that he eventually came to like it. That arc is the whole book: start from honest confusion, follow it through to real understanding. There's no hand-waving, no shortcuts. He shows you the binary, then the assembly, then the JS — and you understand why languages exist.

The ideas that stay

1A program is a building of thought

The introduction shows the same program — add numbers 1 to 10 — written in raw binary, then readable assembly, then JavaScript's while loop, then sum(range(1, 10)). The moral is explicit: "A good programming language helps the programmer by allowing them to talk about the actions the computer has to perform at a higher level" (p. 8). This framing, early and concrete, answers the "why do languages exist" question better than most CS textbooks.

2Functions are values — the real rupture

In many languages, functions are special entities. In JavaScript, a function is a value like any other: you can store it in a variable, pass it as an argument, return it from another function. Haverbeke demonstrates this by storing a function in a binding called launchMissiles, then reassigning it to a no-op based on a condition:

let launchMissiles = () => { /* real launch */ };
if (safeMode) {
    launchMissiles = () => {}; // replaced by a no-op
}
launchMissiles(); // does nothing in safe mode

The fact that this is legal — and useful — is the conceptual key that unlocks the rest of the book.

3Closures: the environment travels with the function

When a function is created, it captures its surrounding scope. multiplier(2) returns a function that multiplies its argument by 2 — and that returned function keeps access to the factor binding even after multiplier has returned:

function multiplier(factor) {
    return number => number * factor;
}
let double = multiplier(2);
double(5); // 10 — factor is still accessible

"A good mental model is to think of function values as containing both the code in their body and the environment in which they are created" (p. 72). This one concept is behind:

  • event handlers (they capture the DOM element or state at the moment they're created);
  • factory functions (each call produces a new function with its own private state);
  • partial application (fix some arguments now, supply the rest later);
  • a large portion of npm packages (most utility libraries are just closures over configuration).

4filter, map, reduce — thinking in transformations

Chapter 5 works through a dataset of human writing scripts (Latin, Arabic, Han...) using higher-order functions. The recipe analogy lands well: detailed step-by-step instructions vs. "soak, simmer, chop, add" — the second requires understanding a few extra words, but says far more in less space. The final example: computing the average year of origin for living scripts fits in a single readable expression:

SCRIPTS.filter(s => s.living).map(s => s.year)

Select, transform, aggregate. That's what thinking in transformations looks like.

5Prototypes and the secret life of this

JavaScript objects are linked to other objects through a prototype chain. When you access a property that an object doesn't have, the runtime walks up the chain until it finds it or reaches null. That's why arrays have .map() and .filter() without you ever defining them — those methods live on Array.prototype, one step up the chain:

Object.getPrototypeOf([]) == Array.prototype        // true
Object.getPrototypeOf(Array.prototype) == Object.prototype // true
Object.getPrototypeOf(Object.prototype)             // null
The prototype chain myArray [0, 1, 2] Array.prototype .map() .filter() .reduce() Object.prototype .toString() .hasOwn() When .map() is not on myArray → look up to Array.prototype → found. myObj = {} plain object Object.prototype null Every object climbs to Object.prototype, then null. Arrays get Object.prototype too.
Property lookup walks up the chain until null

The chapter also clarifies why this behaves differently in arrow functions vs regular functions — arrow functions inherit this from their surrounding scope, regular functions get a new this decided by the caller. That's the source of more confusion than any other single JS feature.

6JavaScript is deliberately liberal — this has costs

The language accepts almost anything you type and interprets it in ways you didn't intend. Handy to start with, treacherous at scale:

counter = 0           // missing "let": global created SILENTLY
0 == false            // true (automatic conversion)
0 === false           // false (=== never converts: always prefer it)
"5" - 1               // 4  ("5" becomes a number)
"5" + 1               // "51" (1 becomes text!) — the classic trap

A NaN ("not a number", the result of an invalid operation like "abc" * 2) then propagates silently until you get wrong output with no error message. Haverbeke names these clearly: "Finding the source of such problems can be difficult" (p. 192). The fix (strict mode, === everywhere, TypeScript) is presented honestly, not evangelized.

7From callback hell to async/await

JavaScript runs on a single thread: it can't block waiting for a network response without freezing the whole page. The chapter walks the full history of how that problem was solved:

  • Callbacks — you pass a function to be called when the result arrives. Simple, but nesting five of them to sequence five operations produces an unreadable pyramid.
  • Promises — a receipt for a future value. Instead of nesting, you chain: .then().then().catch() stays flat. Errors propagate automatically.
  • async/await — syntax sugar over Promises that reads like synchronous code. const data = await fetch(url) looks immediate; the engine handles the Promise underneath.

"An async function implicitly returns a promise and can, in its body, await other promises in a way that looks synchronous" (p. 283). You still need to understand Promises underneath to debug what goes wrong, but async/await is where you live day to day.

Sync vs async: how waiting is handled sync · single thread run waiting… run waiting… async · single thread run network… resume other work The key: waiting is explicit (await), not implicit. Other work runs during the gap.
Sync blocks · async yields and resumes

8Modules: LEGO instead of mud

Before ES modules (2015), all JavaScript ran in a shared global scope, and any two scripts could overwrite each other's variables without warning. The module flips that: each file chooses what it exposes and what it borrows.

// cart.js: this file decides what gets out, the rest stays private
export function total(lines) { ... }
const VAT = 0.2                 // invisible outside: no collision possible

// invoice.js: borrow it by name, the well-defined connector
import { total } from './cart.js'

Haverbeke calls this "LEGO, where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything else" (p. 250): export and import are those connectors. npm ships over three million packages, "a large portion of those are rubbish, to be fair" (p. 254). The honest caveat about npm quality is characteristic of the book's tone.

On the left, a tidy castle built from interlocking bricks; on the right, a mud pile with objects half-sunk in it
Modules according to Haverbeke: well-defined connectors, rather than mud where everything mixes.

Three things I didn't know before reading it

A crow perched on a rooftop antenna, a laptop under its wing and a floating stopwatch, facing a house emitting WiFi waves
Carla, the chapter 11 crow, mid-audit of the neighborhood WiFi (stopwatch at the ready).

My take, honestly

Haverbeke has a selling point nobody else has: he admits he hated JavaScript before loving it (p. 9). So he starts exactly where you start. And his first twelve chapters climb in the right order: functions as values, closures, filter/map/reduce, prototypes. Each concept rests on the previous one, and the exercises don't ask you to copy: they leave you to figure it out, which is where the learning happens.

The browser chapters (13 to 19) are fine but less essential: if your goal is understanding JS, the first twelve are enough. The platform game in chapter 16 is fun, but three chapters for one game is an investment. Diagonal reading allowed, I won't tell anyone.

In 2026, AI generates JavaScript by the kilometer, and that's exactly why you need closures and the prototype chain: they're what lets you review that code and spot the surprises. React itself is just closures in a nice outfit (hooks capture state). And the book that explains all this has been free for fifteen years. There is literally no excuse.

Odilon

Still relevant in 2026?

The fourth edition was published in 2024 and covers ES2024 explicitly. Private class fields (#property), optional chaining (obj?.prop), structuredClone, top-level await — it's all there. The fundamentals chapter (closures, prototypes, event loop) are as relevant as ever because frameworks are built on top of them, not instead of them. The only part that ages is the DOM/browser section, which is still accurate but less representative of how frontend code is written today.

Who is it for?

Read it if

  • You write JavaScript but feel like you're guessing why closures, this, or Promises behave the way they do
  • You're starting out and want a single book that takes you from zero to Node.js, for free
  • You review AI-generated JS and want a solid framework to judge it
  • You like books that assume you're an adult: no hand-holding, real exercises

Skip it if

  • You already understand closures, the prototype chain, and the event loop — you'll know by chapter 3
  • You want a reference you can index and query, not a book you read: the MDN docs serve that role better
  • You're looking for React, Vue, or TypeScript specifically — this book doesn't cover frameworks or typed JS

For going further

The closures and prototypes chapters pair directly with the JavaScript course on this site. For TypeScript (the typed layer above JS), Effective TypeScript by Dan Vanderkam is the natural next step. For modern async patterns in practice, the REST API course covers fetch and async/await in a real context.

Comments (0)

Browse the whole library

More book notes coming: one book at a time, the marrow only.