Skip to content
WestScript
Explaining a tricky concept with one small example

WestScript Editors@build-notesPosted

Explaining a tricky concept with one small example

Some concepts resist explanation. You read three articles about them, nod along, and still get them wrong the next time they appear in your code. Closures, scope, event loops, pointer aliasing, floating-point rounding: most developers have one or two of these that only clicked after a particular example.

That is the clue. When an idea is hard, a well-chosen example does more work than any amount of definition. This post walks through how to build one, using a classic JavaScript puzzle as the running example.

Find the example that surprises people

The best example for a tricky concept is usually the one where the concept causes a bug. People do not remember definitions; they remember the moment the code did something they did not expect.

For closures in JavaScript, a famous candidate looks like this:

js
const handlers = [];
for (var i = 0; i < 3; i++) {
  handlers.push(() => i);
}
console.log(handlers.map((h) => h()));
// [ 3, 3, 3 ]

Most people expect [0, 1, 2]. The gap between what they expect and what they get is exactly the shape of the concept you are about to explain. If you can find an example with that kind of gap, half the post is already written.

How do you find one? Think back to the first time the concept bit you, look through old bug fixes in your own projects, or search the questions people ask about it. The surprise is usually the same one for everybody.

Strip it down until nothing is left but the idea

Real bugs come wrapped in real code: frameworks, network calls, half a page of setup. For an explanation, remove all of it. The example above has no DOM, no timers and no event listeners, even though the bug most often shows up with click handlers or setTimeout. Those details add noise without adding understanding.

A good test is to delete one line at a time and ask whether the surprise still happens. If it does, the line was not needed. Stop when every remaining line is load-bearing. Five lines is a good target; fifteen is usually too many.

Make sure the stripped-down version still runs and still prints what you claim. Paste it into a fresh console or file and copy the real output into the post, rather than typing what you think it will print.

Ask the reader to predict, then show the result

Before revealing the output, invite the reader to guess. In writing, that can be as simple as a sentence: "Before you scroll, what do you think this prints?" It costs nothing and it changes how people read. A reader who has committed to a guess is paying attention when the answer arrives.

Then show the output and acknowledge the surprise. If the result is counter-intuitive, say so. Telling readers "this is obviously [3, 3, 3]" makes the ones who guessed wrong feel slow, and they stop reading.

Build the mental model in small steps

Now explain why. Resist the urge to recite the formal definition first. Walk through what the program actually does, in the order it does it:

  1. A var declaration is scoped to the enclosing function (or the script), not to the loop body. There is one i for the whole loop.
  2. Each arrow function captures a reference to that variable, not a copy of its value at the time the function was created. That captured reference is the closure.
  3. The loop runs to the end. When it stops, i is 3.
  4. Only then do we call the functions. All three look up the same i and find 3.

After the steps, give the one-sentence version: a closure remembers variables, not values. A reader who followed the steps can now hold that sentence and know what it means. The definition arrives last, as a summary, not first, as a barrier.

If you want to point to a reference, MDN's guide to closures is thorough and accurate.

Show the fix, then one variation

An explanation feels finished when the reader can act on it. Show the smallest change that produces the expected result:

js
const handlers = [];
for (let i = 0; i < 3; i++) {
  handlers.push(() => i);
}
console.log(handlers.map((h) => h()));
// [ 0, 1, 2 ]

One word changed. With let, each iteration of a for loop gets a fresh binding of i, so each function closes over a different variable. The mental model from the previous section still holds; only the number of variables changed.

Then add one variation that tests the model. For example: what happens if the loop uses let but the functions are called inside the loop instead of afterwards? A reader who can predict that correctly has understood the concept, not just memorised the fix. One variation is enough. A whole quiz turns an explainer into homework.

Things that make explanations worse

A few habits reliably muddy a concept post:

  • Too many examples. Each new example asks the reader to load a new context. One main example, reused and varied, is easier to follow than five unrelated ones.
  • Analogies that leak. "A closure is like a backpack" helps for a moment and then misleads, because backpacks hold copies of things. If you use an analogy, say where it breaks.
  • Jargon before need. Words like "lexical environment" are correct, but introduce them after the reader has seen what they describe, if at all.
  • Skipping the run. An example with a wrong output in the post undermines every sentence around it.

Concept posts like this often begin life as a much shorter note. If you found the puzzle by accident this week, it might make a good "Today I Learned" post first, with the full explanation following later.

Discussion 0

Replies are reviewed before they appear.

Write a reply

Plain text, up to 2,000 characters. No account or email needed; a moderator reviews each reply first.

No replies yet. Found a typo, a better approach or a step that failed for you? Start the thread.