Before you begin
Why this matters
Most expressions give the same value whatever order their parts run in. The ones that do not are the ones with side effects: a call that prints, or a counter that changes. When you know the rule, you can predict them, and you can see when an expression should be split.
What this helps you reason about
- Predict the order in which the calls in one expression print.
- Group a row such as a - b + c, or 2 ** 3 ** 2, without guessing.
- Spot an expression that reads and changes the same variable.
An expression with several operators and calls has two questions: how it groups, and in which order its parts run. This lesson separates them. It shows how a row of operators with the same precedence groups, and the order in which TypeScript computes operands and arguments.
Already know this? The short version
- Precedence and associativity decide the grouping; the evaluation order decides what is computed first. They are separate rules.
- Operators of one precedence group from the left: a - b + c is (a - b) + c. ** groups from the right: 2 ** 3 ** 2 is 512.
- TypeScript computes operands and arguments from left to right, and each side effect happens when its part is computed. tag++ * 10 + tag with tag 4 gives 45.
- Every argument is computed before the body starts, even one the function ignores. When a result depends on the order, split the expression into statements.
Words in this lesson (6)
- operator precedence
- The grammar rule that decides which operators in an expression are applied first. * and / come before + and -, so 6 + 3 / 2 means 6 + (3 / 2).
- associativity
- The grammar rule that decides how operators of the same precedence group. A left-associative operator groups from the left: a - b - c means (a - b) - c.
- operand
- A value an operator works on, such as 640 and 72 in 640 + 72. An operand can itself be an expression, such as a call.
- evaluation order
- The order in which the parts of an expression are computed when the program runs. Precedence and associativity decide how the parts group, not this order.
- side effect
- A change that code makes besides giving its value, such as changing a variable or printing a line.
- argument
- A value written in a call, such as 3 in area(3, 4). Each argument is computed before the function’s body starts.
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 / Grouping
Does precedence decide what runs first?
Syntax and meaning showed that precedence decides how an expression groups. In seed(1) + water(3) * 2, the * gets water’s result and 2, and the + gets seed’s result and that product. Expressions and statements called the values an operator works on its operands, and showed that a call can print while it computes its value. This lesson asks when each part runs.
Grouping does not decide timing. An operator can be applied only after its operands are computed, so the * is applied before the +. But seed(1) is an operand of the +, and nothing stops it from being computed before everything else. The order in which the parts of an expression are computed is the evaluation order. It is a separate rule, and each language has its own. TypeScript computes the operands of an expression from left to right. So seed(1) runs and prints seed; then water(3) runs and prints water; then the * is applied, and then the +.
function seed(trays: number): number {
console.log("seed");
return trays * 3;
}
function water(cans: number): number {
console.log("water");
return cans + 1;
}
console.log(seed(1) + water(3) * 2); // seed, water, 11 Precedence decides the grouping, not the timing. The evaluation order decides which part is computed first.
Experiment
Watch which call runs first
Precedence groups the expression as seed(1) + (water(3) * 2): the * gets water’s result and 2. It says nothing about time. TypeScript computes the operands of an expression from left to right. So seed runs, then water, then the *, then the +.
Why it matters Read the grouping from precedence and the order from the language’s evaluation rule. They answer two different questions.
* is applied before +. Predict whether seed or water is printed first, and the total. Then change line 9 to const total = (seed(1) + water(3)) * 2;, and predict again.
function seed(trays: number): number {
console.log("seed");
return trays * 3;
}
function water(cans: number): number {
console.log("water");
return cans + 1;
}
const total = seed(1) + water(3) * 2;
console.log("total:", total);
What to look for
Step from line 9. The frame list shows seed above <top level> first, and seed is printed; then water takes its place, and water is printed. total shows not initialized yet until the stop on line 10, and the run prints total: 11. Node stops on some lines twice; that is not a second call. After the edit, the calls come in the same order, and the run prints total: 14.
Expected output
seed
water
total: 1102 / Associativity
How do operators of the same precedence group?
When two operators have the same precedence, a second rule decides the grouping: associativity. Most operators are left-associative: they group from the left. 20 - 5 - 3 means (20 - 5) - 3, which is 12, not 20 - (5 - 3), which is 18.
A row of + and - groups the same way. stock - planted + lost means (stock - planted) + lost: the minus applies to planted only, and lost is added. To take away both, write stock - planted - lost, or stock - (planted + lost).
** is the exception. It is right-associative, as powers are in mathematics: 2 ** 3 ** 2 means 2 ** (3 ** 2), which is 512.
The + that joins text also groups from the left. "trees: " + 3 + 4 gives "trees: 34": the first + joins the text and 3, and the second joins that text and 4. 3 + 4 + " trees" gives "7 trees", because the first + adds two numbers.
console.log(20 - 5 - 3); // 12
console.log(20 - (5 - 3)); // 18
console.log(2 ** 3 ** 2); // 512
console.log("trees: " + 3 + 4); // trees: 34
console.log(3 + 4 + " trees"); // 7 trees Operators of the same precedence group from the left, one at a time. Associativity, like precedence, decides grouping, not timing.
Coming from Python: Python refuses "trees: " + 3. TypeScript turns the number into text, and the row groups from the left.
Precisely: grouping is still not timing
In a() ** b() ** c(), the right ** is applied first, but the calls still run from left to right: a, then b, then c. Associativity changes which operator gets which operands, not when the operands are computed.
Experiment
Group a row of ** operators
** groups from the right, as powers do in mathematics: 2 ** 3 ** 2 means 2 ** (3 ** 2), which is 2 ** 9. Almost every other operator groups from the left.
Why it matters When you write two ** in a row, the right one is applied first. Add parentheses when you mean the other grouping.
Predict whether power is (2 ** 3) ** 2, which is 64, or 2 ** (3 ** 2), which is 512. Then put parentheses around 2 ** 3 on line 1, and predict again.
const power = 2 ** 3 ** 2;
console.log("power:", power);
What to look for
Step to line 2: power shows 512, and the run prints power: 512. After the edit, power shows 64.
Expected output
power: 51203 / Operands
Which operand is computed first?
TypeScript computes the operands of an expression from left to right, and each side effect happens as soon as its part is computed. The ECMAScript specification fixes this order. So an expression that reads and changes one variable has a defined result, but maybe not the one you expect. With tag at 4, tag++ * 10 + tag gives 45: tag++ gives 4 and stores 5, and the tag on the right reads 5. tag + tag++ * 10 gives 44.
When a calculation depends on which side runs first, give each step its own statement. The order is then the order of the lines, and a reader does not need the rule to see it.
let tag = 4;
const old = tag;
tag++;
const label = old * 10 + tag; // 45, and easy to see When a result depends on the order of its operands, split the expression into statements.
Coming from C: In C, f() + g() may call g first. TypeScript always calls f first.
Precisely: compound assignment
In total += f(), TypeScript reads total before f runs. If f changes total, that change is lost when the sum is stored. Calling f in a statement of its own avoids the question.
Experiment
Read a counter and change it in one expression
The operands are computed from left to right, and each side effect happens as soon as its part is computed. tag++ gives 4 and stores 5 at once, so the tag on the right reads 5: 4 * 10 + 5 is 45. After the edit, the tag on the left reads 4 before tag++ runs: 4 + 4 * 10 is 44.
Why it matters The order is defined, but a reader must know it to see the result. Give the read and the change their own statements.
tag is 4. Predict label: does the tag on the right read 4 or 5? Then change line 2 to const label = tag + tag++ * 10;, and predict again.
let tag = 4;
const label = tag++ * 10 + tag;
console.log("label:", label, "tag:", tag);
What to look for
Step to line 3: label shows 45 and tag shows 5. The run prints label: 45 tag: 5. After the edit, label shows 44, and tag still ends at 5.
Expected output
label: 45 tag: 504 / Arguments
When are the arguments of a call computed?
A call is an expression too, and its arguments are its parts. TypeScript computes the arguments of a call from left to right. The function’s body starts only after every argument has a value. So an argument is computed even when the function never uses it: below, firstOf ignores b, and sow still runs and prints. Function calls follows one call step by step.
function dig(): number {
console.log("dig");
return 1;
}
function sow(): number {
console.log("sow");
return 2;
}
function firstOf(a: number, b: number): number {
return a;
}
console.log(firstOf(dig(), sow())); // dig, sow, 1 Every argument is computed before the body starts, even an argument the function never uses.
05 / Try it
Can you explain a fresh example?
Predict the output before you reveal the answer. Find the grouping first, then the order in which the parts run.
function pour(litres: number): number {
console.log("pour", litres);
return litres;
}
const left = 20 - pour(5) - pour(3) * 2;
console.log("left:", left, 2 ** 2 ** 3); What does the program print?
Reveal the reasoning
pour 5, pour 3, then left: 9 256. The operands are computed from left to right, so pour(5) runs before pour(3), although the * is applied before either -. The grouping is (20 - 5) - (3 * 2), which is 9. ** groups from the right: 2 ** (2 ** 3) is 2 ** 8, which is 256.
The next lesson, What is a value?, looks at the values these expressions give and what their types decide.
Practice tickets
- EVAL-101 · Saplings given away or lost make the nursery count go up → Free
- EVAL-102 · Rainy days show a negative change in the rain gauge → Sign in · Pro
- EVAL-103 · The tap pump runs on days the rain barrel fills the can → Sign in · Pro