Before you begin
Why this matters
Programs are often written from what one run showed. That works until another computer, another version, or another input shows something else. If you know which statements the language promises, you know which observations you can build on.
What this helps you reason about
- Say whether a statement about a program is a promise of the language, a picture that helps you think, or what one compiler or runtime happened to do.
- Spot output that depends on an order or an address that the language does not promise, and rewrite it so that it sorts or compares values.
- Test a belief by changing one thing at a time and by trying inputs that could prove it wrong.
A TypeScript program runs on Node.js, and the stepper shows what Node.js did. This lesson sorts what you see into three kinds: what JavaScript promises, what a picture suggests, and what Node.js happened to do.
Already know this? The short version
- A run shows what Node.js did this time; the ECMAScript specification and the documentation say what is promised.
- Arrays keep their order, and a Map and a Set keep insertion order. An object lists keys that look like whole numbers first, from smallest to largest, then the others in insertion order.
- How console.log lays out an object, the wording of error messages, and an array’s spare room are details of Node.js.
- The stepper’s panel is a summary: an array of more than ten values shows ten and a count.
- Test a belief by changing one thing at a time and by looking for a counterexample.
Words in this lesson (7)
- observable behavior
- What a program does that the language promises: the text it prints, the values its functions return, and the errors it reports. A program that depends only on promises behaves the same with every correct compiler or runtime.
- teaching model
- A simplified picture of how a program works, such as a box for each variable, an arrow for a reference, or a list of frames. It helps you predict behavior and leaves details out.
- implementation detail
- Something one compiler or runtime does that the language does not promise, such as the order of a hash map or the address of a variable. Another version, or another run, may do it differently.
- object
- A value that groups named properties, written with braces: { value: 3 } is an object with one property, value.
- insertion order
- The order in which keys were first added to a map. Storing a new value under a key that is already there does not change it.
- debugger
- A tool that runs a program and pauses it at chosen places, so you can look at the values it holds. Each pause is a stop.
- counterexample
- One input for which a belief about a function is false. A single counterexample is enough to show that the belief is wrong.
Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.
01 / Three kinds
Which kind of statement is this?
Tracing a small program used the stepper to watch a program run. This lesson asks what such a run proves.
Every statement about a program is one of three kinds. Observable behavior is what the language promises: the text a program prints, the values its functions return, and the errors it reports. A teaching model is a simplified picture that helps you think, such as a box for each variable or the stepper’s list of frames. An implementation detail is what one compiler or runtime does that the language does not promise, such as where a value is kept in memory.
Each kind needs its own treatment. Build your program on promises. Use a model to predict, and correct the model when it disagrees with a promise. Treat a detail as true for this tool, this version, and perhaps only this run.
const spools = [3, 5, 2];
// Promise: this prints 10, every time.
console.log(spools[0] + spools[1] + spools[2]);
// Model: spools is a row of three boxes.
// Detail: where Node.js keeps the boxes,
// and how much room it leaves for more. Ask of every statement: does the language promise it, does a picture suggest it, or did one tool happen to do it?
Precisely: where the promises are written
JavaScript’s promises are in the ECMAScript specification; TypeScript adds type checks and removes them before the run. Node.js documents its own functions, such as console.log. A promise in documentation is still a promise: MDN and the specification both say that a Map keeps insertion order.
02 / One run
What does one run prove?
A run of a program is an observation. It shows what this compiler or runtime did, with this input, on this computer, this time. One run proves that something can happen. It does not prove that it always happens.
When the language promises a result, every run gives it. When the language leaves a choice open, the compiler or the runtime makes the choice. It may choose differently in another version, on another computer, or in the next run.
In TypeScript, most orders are promised. The details are elsewhere. How console.log lays out an object is Node.js’s choice: { colour: 'red', metres: 40 }, with spaces and single quotes. A browser shows the same object differently. The exact words of an error message are Node.js’s too, and so is how much spare room an array keeps for more values.
const spool = { colour: "red", metres: 40 };
// Promise: this prints 40.
console.log(spool.metres);
// Detail: the layout of the whole object,
// { colour: 'red', metres: 40 } in Node.js.
console.log(spool); One run proves “this can happen”, never “this always happens”.
Coming from a browser console: console.log shows an object differently in a browser and in Node.js. Print the values you need, not the layout of the whole object.
03 / Promises
How do you depend only on what is promised?
Write the program so that its output depends only on promises. When the order of a collection is not promised, sort before printing. When you want to know whether two names lead to one object, compare them with ===; do not rely on how console.log shows them. When you want to know how many, count.
Object.keys(spools) gives an object’s keys in an array. sort() puts an array of texts in alphabetical order, character by character, and join(" ") makes one text from the array, with a space between the values. The sorted output is the same whatever order the keys were added in.
The trap in TypeScript is a rule guessed from one example. An object seems to keep its keys in the order you write them, until a key looks like a whole number. Such keys come first, from smallest to largest.
const spools: Record<string, number> = { red: 3, blue: 5 };
// Alphabetical, whatever order the keys came in:
console.log(Object.keys(spools).sort().join(" "));
// blue red Remove the detail from the path to the output: sort, compare, or count.
Coming from Python: A Python dict keeps insertion order for every key. A JavaScript object moves keys that look like whole numbers, such as "310", to the front.
Coming from Java: A JavaScript Map is ordered like a LinkedHashMap, not like a HashMap: its insertion order is a promise.
Precisely: what sort() compares
sort() with no compare function turns each value into text and compares the texts by their UTF-16 code units. So capital letters come before lowercase ones, and [10, 9].sort() gives [10, 9]. For numbers, pass a compare function: sort((a, b) => a - b).
Experiment
Add a key that looks like a number
The order of an object’s keys is a rule of JavaScript, but not the rule the first run suggests. Keys that look like whole numbers, such as "310", come first, from smallest to largest. The other keys follow in the order they were added. The first program had no such keys, so objects seemed to keep the order you write.
Why it matters One example showed a pattern; the rule was wider. When the order matters, sort: Object.keys(spools).sort() gives the keys in alphabetical order, whatever they look like.
Record<string, number> is the type of an object whose keys are texts and whose values are numbers. Object.keys gives the keys in an array. Predict the output. Then add the line "310": 4, after green: 2, (a thread known only by its colour number), and predict again.
const spools: Record<string, number> = {
red: 3,
blue: 5,
green: 2,
};
const colours = Object.keys(spools);
console.log(colours.join(" "));
What to look for
Before the edit, the panel shows colours as ["red", "blue", "green"], and the run prints red blue green; Node can stop twice on one line. After the edit, the panel shows spools as {310: 4, red: 3, blue: 5, green: 2}. The run prints 310 red blue green.
Expected output
red blue green04 / Pictures
What do pictures and the stepper leave out?
A teaching model is a picture: a box for each variable, an arrow from a name to an object, a list of frames for the calls in progress. Lessons use models because a picture makes a rule easy to remember.
A model leaves things out on purpose. A box has no address. The picture does not show that the compiler can keep a value in a processor register, or remove a variable it does not need. When a model and a promise disagree, the promise wins, and the model needs a correction.
The stepper is a model too. It runs your program under a debugger, a tool that pauses the program so you can look at its values, and its panels show those values in a format the stepper chose.
The TypeScript stepper shows an array of more than ten values as its first ten and a count, such as … 12 in all. It shows a let or const whose line has not run as not initialized yet. Node can stop more than once on the same line. None of this changes what the program does.
// At the first stop, the panel shows
// total as not initialized yet.
const total = 3 + 5;
console.log(total); // 8 A picture, and the stepper’s panels, show a chosen view of the program; the promises decide what it does.
Experiment
Find a value that the panel does not list
The panel is a summary that the stepper chose, so that it fits on a screen. The array still holds all 12 values, and console.log reads them from the program, not from the panel.
Why it matters Read the panel as a picture of the values, not as the values themselves. When one value decides your answer, print it.
Predict both lines. While stepping, look for the last value, 5, in the variables panel. Then delete 55, 5 from line 1 and predict again.
const lengths = [40, 25, 60, 10, 35, 50, 20, 45, 30, 15, 55, 5];
console.log("spools:", lengths.length);
console.log("last:", lengths[lengths.length - 1]);
What to look for
The panel shows lengths as [40, 25, 60, 10, 35, 50, 20, 45, 30, 15, … 12 in all]: ten values and the count. The run still prints spools: 12 and last: 5. After the edit, the panel lists all ten values, and the run prints spools: 10 and last: 15.
Expected output
spools: 12
last: 505 / Beliefs
How do you test a belief about a program?
A belief is a statement you think is true about a program, such as “total adds all three numbers”. A run that agrees with a belief is weak evidence. total(2, 4, 4) gives 10, the right answer, although the code adds b twice: in that input, b and c happen to be equal. A counterexample is one input for which the belief is false. total(2, 4, 7) is one.
To test a belief, try inputs that could prove it wrong: other values, a different order, an empty list, a single item, an item that repeats. Change one thing at a time, then compare. If you change the input and the code together and the output changes, you cannot tell which change did it.
A belief about an implementation detail, such as an order, can pass many runs and fail on the next computer. More runs do not fix that; code that does not depend on the detail does. A later lesson, Assertions, tests, and counterexamples, turns beliefs into tests that run every time.
// Belief: total adds all three numbers.
function total(a: number, b: number, c: number): number {
return a + b + b;
}
console.log(total(2, 4, 4)); // 10: looks right
console.log(total(2, 4, 7)); // 10: should be 13 One counterexample proves a belief wrong; many agreeing runs do not prove it right.
06 / Try it
Can you explain a fresh example?
Predict the output before you reveal the answer. Then sort each line: is it a promise of the language or its library, or a detail of this compiler or runtime? A Set is a collection that holds each value at most once. Array.from makes an array from its values.
const threads = new Set(["wool", "silk", "wool", "linen"]);
console.log(threads.size);
console.log(Array.from(threads).join(" ")); What does the program print, and which lines would every correct compiler or runtime print the same?
Reveal the reasoning
3, then wool silk linen. Both lines are promises. A Set holds each value once, so the second "wool" is not added, and size is 3. A Set gives its values in insertion order, and a value added again keeps its first place, so wool stays first. Every correct JavaScript engine prints the same two lines.
A rule guessed from this run would still need care: an object, unlike a Set, moves keys that look like whole numbers to the front.
The next lesson, What is a value?, looks at the values that these programs print.
Practice tickets
- OBSERVE-101 · The mineral card lists names out of alphabetical order → Free
- OBSERVE-102 · The wax calculator orders too few blocks for most batches → Sign in · Pro
- OBSERVE-103 · The swap checker accepts a bundle with the wrong copies → Sign in · Pro