cf.completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Evaluation order

See why a + b * c still runs a first, why x += f() reads x before the call, and how ||, ?? and ?. skip work, with puzzles like a[i++] = i.

By the end, you can
  • 01
    Predict the orderSay which part of an expression runs first, and why precedence doesn't change that order.
  • 02
    Trace assignments and callsFollow =, +=, function arguments, and computed keys step by step, from left to right.
  • 03
    Spot skipped workTell when &&, ||, ??, and ?. skip the right side, and solve puzzles like a[i++] = i.

Which part of an expression runs first

An expression is a piece of code that gives a value, like 2 + 3 or getPrice(). Most expressions have several parts. JavaScript works out the parts one at a time, in a fixed order. Working out a part to get its value is called evaluating it. This lesson is about that order: which part runs first, and which runs next.

Often the order makes no difference. It matters when a part does something besides giving a value: it prints, changes a variable, or reads the next item from a list. That extra work is called a side effect. To make the order visible, many examples in this lesson use a small helper called say.

Three calls in one sumPop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
console.log(say("a", 2) + say("b", 3) * say("c", 4));

Lines 1 to 4 make the helper. Line 2 prints the label it was given, and line 3 hands back the value. Many examples in this lesson start with the same four lines, so their own code begins on line 5.

Line 5 calls say three times. The * joins 3 and 4 before the + adds 2, so you might expect b and c to run first. They don't. The program prints a, then b, then c, and then 14.

The three say calls in line 5 run in order: a first and gives 2, b second and gives 3, c third and gives 4. Then the result, 14, is worked out.say("a", 2) + say("b", 3) * say("c", 4)abc141stgives 22ndgives 33rdgives 4result
The three calls run left to right: a, then b, then c. Only then does JavaScript work out 3 * 4 = 12 and 2 + 12 = 14.
Real-life analogyA shopping list and its bags

You have a shopping list: bread, milk, eggs. A note at the bottom says "milk and eggs go in the same bag". You still walk down the list from the top and pick up bread first. The note only changes how the items are packed, not the order you pick them up.

In real life: Pick up the items in the order they are written
In JavaScript: Run each part from left to right: a, b, c
In real life: The note says which items share a bag
In JavaScript: Precedence and brackets say which values are joined
In real life: The packed bags at the till
In JavaScript: The final value, 14
The main idea

Evaluation order is the order in which JavaScript works out the parts of an expression. The rule is simple: from left to right, one part at a time, and each part is finished before the next one starts. Precedence and brackets decide which values are joined together. They don't change which part runs first. A few operators, like &&, ||, ?? and ?., may skip a part completely.

The ECMAScript specification, the rulebook every JavaScript engine follows, writes this order down for every kind of expression. So Chrome, Firefox, Safari and Node.js all print the same lines, in the same order.

Left to right, whatever the precedence

Precedence is the rule that says which operator joins its values first. * comes before +, so 2 + 3 * 4 means 2 + (3 * 4). Precedence answers the question "which values belong together?" It doesn't answer "which part runs first?". That answer is always: the one on the left.

Step through a + b * c
Step 0 of 11Ready
Your turn: follow the blue line

A replay of the example code, not a view inside the JavaScript engine.

Running in
  1. script
Next: line 5
function say(label, value) {  console.log(label);  return value;}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Press Step to walk through line 5 of the first example. Each say call starts, prints its label, and hands back its value, from left to right. Only after all three calls does the last step work out 3 * 4 = 12 and 2 + 12 = 14. This replays the example code; it isn't a view inside the JavaScript engine.

Brackets change the answer, not the orderPop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
console.log((say("a", 2) + say("b", 3)) * say("c", 4));

Lines 1 to 4 make say. Line 5 puts grouping brackets around say("a", 2) + say("b", 3), so 2 and 3 are added first, and then multiplied by 4. The program prints a, b, c, and then 20. The answer changed from 14 to 20, but the three calls ran in the same order.

Power groups from the right, but runs from the leftPop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
console.log(say("a", 2) ** say("b", 3) ** say("c", 2));

Line 5 uses **, the power operator. It is unusual: it groups from the right, so 2 ** 3 ** 2 means 2 ** (3 ** 2), which is 2 ** 9, or 512. Even so, the calls still run a, b, c. The program prints those three labels, then 512.

The table below compares the three examples. Read each row as: "when I write this, the parts are grouped like this, and this part runs first."

Grouping and running order are separate questions
When you writeHow the parts are groupedWhich part runs first
a + b * cb * c is grouped first, then added to a.a runs first, then b, then c.
(a + b) * cThe grouping brackets make a + b one group.a runs first, then b, then c.
a ** b ** cb ** c is grouped first, because ** groups from the right.a runs first, then b, then c.

Next, here is how the specification describes it. Spec steps read like a recipe. "Let x be …" means "call the result x". A ? means "if this step goes wrong, stop and throw the error". "Evaluation" means "run this part", and GetValue means "read its value". After each box, the same steps follow in plain words.

How a math operator runs: shortened from the specText
Let lref be ? Evaluation of the left side.
Let lval be ? GetValue(lref).
Let rref be ? Evaluation of the right side.
Let rval be ? GetValue(rref).
Return ? ApplyStringOrNumericBinaryOperator(lval, opText, rval).
// Shortened from the spec's steps for +, -, * and the other math operators.

These are the spec's steps from EvaluateStringOrNumericBinaryExpression, which every math operator uses. In plain words, line by line:

  1. Run the left side. For say("a", 2), that prints a.
  2. Read the left side's value, 2, and keep it.
  3. Only now run the right side.
  4. Read the right side's value.
  5. Join the two values with the operator, such as + or *, and hand back the answer.
  6. The last line is a note, not a step.

So the left side is finished, value and all, before the right side starts. In a + b * c, the right side of + is the whole group b * c. That group runs second, and inside it, b runs before c.

Assignment: the left side goes first

An assignment writes a value into a place, like price = 4 or cart.price = 4. It's tempting to think the right side runs first, because its value is needed. But JavaScript first works out the left side, to know where to write.

The left side of = runs before the right sidePop out in the code editor (opens in a new tab)JavaScript
const cart = { price: 3 };
function getCart() {
  console.log("get cart");
  return cart;
}
function getPrice() {
  console.log("get price");
  return 4;
}
getCart().price = getPrice();
console.log(cart.price);

Line 1 makes a cart with a price of 3. Lines 2 to 5 make getCart, which prints "get cart" and hands back the cart. Lines 6 to 9 make getPrice, which prints "get price" and hands back 4.

Line 10 is the assignment. getCart() runs first and prints "get cart": JavaScript needs to know which object to write into. Then getPrice() runs and prints "get price". Then 4 is written into the cart's price. Line 11 prints 4.

How = runs: shortened from the specText
Let lref be ? Evaluation of the left side.
Let rref be ? Evaluation of the right side.
Let rval be ? GetValue(rref).
Perform ? PutValue(lref, rval).
Return rval.
// Shortened from the spec's steps for =.

These are the spec's assignment steps. In plain words, line by line:

  1. Run the left side, only to find the place to write: "the price property, inside the cart". Nothing is read from it.
  2. Run the right side.
  3. Read the right side's value, 4.
  4. Write that value into the place found in step 1. PutValue means "write into this place".
  5. The whole assignment gives back the value it wrote.
  6. The last line is a note, not a step.

So the place is found first, the value second, and the write happens last. The full steps also cover patterns like [a, b] = list; the box shows only the plain case.

Compound assignments, like +=, -= and *=, do one more thing: they read the old value. The question is when.

+= reads the old value before the callPop out in the code editor (opens in a new tab)JavaScript
let total = 3;
function addTea() {
  total = 10;
  return 4;
}
total += addTea();
console.log(total);

Line 1 gives total the value 3. Lines 2 to 5 make addTea: line 3 sets total to 10, and line 4 hands back 4.

Line 6 is total += addTea(). JavaScript reads the old total first, 3, and keeps it. Then it calls addTea(), which sets total to 10 and hands back 4. Then it adds the kept 3 and the 4, and writes 7 into total. Line 7 prints 7, not 14.

Line 6, total += addTea(), in three steps. Step 1 reads total, which holds 3. Step 2 calls addTea, which sets total to 10 and gives 4. Step 3 writes 3 + 4, so total holds 7.total += addTea()3totalsets total to 10addTea()7total1. read 32. call, get 43. write 3 + 4
The old value, 3, is read before addTea() runs. So the 10 that addTea() writes is lost: step 3 writes 3 + 4 = 7 over it.
Step through total += addTea()
Step 0 of 7Ready
Your turn: follow the blue line

A replay of the example code, not a view inside the JavaScript engine. The real += runs on a small helper that reports each read and write of total as it happens.

Running in
  1. script
Next: line 1
function addTea() {  total = 10;  return 4;}total += addTea();console.log(total);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Step through line 6 and watch total. It is read as 3, changed to 10 by line 3, and then written over with 7. This replays the example code; it isn't a view inside the JavaScript engine. The real += runs on a small helper that reports each read and write as it happens.

The long form gives the same answerPop out in the code editor (opens in a new tab)JavaScript
let total = 3;
function addTea() {
  total = 10;
  return 4;
}
total = total + addTea();
console.log(total);

This is the same program, but line 6 is written out in full: total = total + addTea(). The + runs its left side first, so total is read as 3 before addTea() runs. Line 7 prints 7 again. The long form and += follow the same order.

How += runs: shortened from the specText
Let lref be ? Evaluation of the left side.
Let lval be ? GetValue(lref).
Let rref be ? Evaluation of the right side.
Let rval be ? GetValue(rref).
Let r be ? ApplyStringOrNumericBinaryOperator(lval, opText, rval).
Perform ? PutValue(lref, r).
Return r.
// Shortened from the spec's steps for +=, -=, *= and the others.

In plain words, line by line:

  1. Run the left side to find the place: the variable total.
  2. Read the old value from that place, 3, and keep it.
  3. Run the right side. This is where addTea() is called and sets total to 10.
  4. Read the right side's value, 4.
  5. Join the kept 3 and the 4 with the operator: 3 + 4 = 7.
  6. Write 7 into the place from step 1.
  7. The whole assignment gives back 7.
  8. The last line is a note, not a step.

Step 2 comes before step 3. That's why a change made by the right side can't reach the old value: it was already read.

Function arguments and computed keys

A function call has parts too: the function itself, and each argument, the values inside the call brackets. JavaScript first finds the function, then runs the arguments from left to right, and only then makes the call.

The function is found before the arguments runPop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
function pickAdd() {
  console.log("pick add");
  return (x, y) => x + y;
}
console.log(pickAdd()(say("first", 2), say("second", 3)));

Lines 1 to 4 make say. Lines 5 to 8 make pickAdd: line 6 prints "pick add", and line 7 hands back a small function that adds two numbers.

Line 9 calls pickAdd() first, to find the function to call. Then it runs the first argument, which prints "first", and the second, which prints "second". Then it calls the adding function with 2 and 3. The program prints "pick add", "first", "second", and then 5.

The arguments even run before JavaScript checks that the function really is a function. The next example calls a method that doesn't exist.

Arguments run before the "is it a function?" checkPop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
const tools = {};
try {
  tools.format(say("argument", 3));
} catch (error) {
  console.log(error.name);
}

Line 5 makes an empty tools object, with no format method. Line 7 reads tools.format, which gives undefined. Then it runs the argument, and "argument" is printed. Only then does JavaScript check whether undefined can be called. It can't, so it throws a TypeError. Lines 8 and 9 catch the error and print its name, TypeError.

How a call runs: EvaluateCall, shortened from the specText
Let ref be ? Evaluation of MemberExpression.
Let func be ? GetValue(ref).
Let argList be ? ArgumentListEvaluation of Arguments.
If IsCallable(func) is false, throw a TypeError exception.
Return ? Call(func, thisValue, argList).
// Shortened from the spec's steps for a function call.

These steps come from the spec's function call steps and EvaluateCall. In plain words, line by line:

  1. Run the part before the call brackets, like tools.format or pickAdd().
  2. Read its value and call it func. For tools.format, that is undefined.
  3. Run every argument, from left to right, and keep their values in a list.
  4. Only now check that func can be called. If it can't, throw a TypeError.
  5. Make the call, with the argument values from step 3.
  6. The last line is a note, not a step.

So a call to a missing method still runs its arguments first. If an argument prints, saves, or sends something, that happens before the error.

Objects follow the same left-to-right rule. A computed key is a property name in square brackets, like [name]: 3. JavaScript runs the code inside the brackets and uses the result as the property name.

Each key runs just before its valuePop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
const order = {
  [say("key 1", "tea")]: say("value 1", 3),
  [say("key 2", "cake")]: say("value 2", 4),
};
console.log(order.tea, order.cake);

Line 5 starts the order object. Line 6 runs its key first, which prints "key 1" and gives "tea", and then its value, which prints "value 1" and gives 3. Line 7 does the same for the second property: "key 2", then "value 2". Line 9 prints 3 4.

Building the order object. First key 1, tea, then value 1, 3. Then key 2, cake, then value 2, 4. An arrow puts them into the order cart, which holds tea: 3 and cake: 4.key 1"tea"value 13key 2"cake"value 24tea: 3cake: 4order
Each property is finished before the next one starts: its key first, then its value. The labels match what the calls on lines 6 and 7 print.

So JavaScript doesn't run all the keys first and then all the values. It finishes one property, key and value, before it starts the next.

The table below collects the order for assignments, calls, and object properties. Read each row as: "when I write this, these parts happen in this order."

What runs first in assignments, calls, and objects
When you writeWhat happens, in order
x = f()JavaScript finds where x lives, then calls f(), then writes the answer into x.
getCart().price = getPrice()getCart() runs first, then getPrice(), then the price is written into the cart.
x += f()JavaScript reads the old x first, then calls f(), then writes the sum into x.
f(a, b)JavaScript finds f first, then works out a, then b, then makes the call.
{ [k]: v }JavaScript works out each key, then that key's value, one property at a time.

Skipping work: &&, ||, ?? and ?.

Some operators look at their left side first and may decide they don't need the right side at all. Skipping the right side like this is called short-circuiting. The skipped part doesn't run later. It never runs.

These operators check whether a value is truthy or falsy. A falsy value is one that counts as "no": false, 0, "", null, undefined, and NaN. Every other value is truthy.

|| skips the right side when the left side is truthyPop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
const name = say("left", "Asha") || say("right", "guest");
console.log(name);

Line 5 runs the left side first: it prints "left" and gives "Asha". "Asha" is truthy, so || already has its answer. The right side is skipped, so "right" is never printed. Line 6 prints Asha.

How || runs: the steps from the specText
Let lref be ? Evaluation of the left side.
Let lval be ? GetValue(lref).
If ToBoolean(lval) is true, return lval.
Let rref be ? Evaluation of the right side.
Return ? GetValue(rref).
// The spec's steps for ||.

These are the spec's steps for the logical operators. ToBoolean means "is this value truthy?". In plain words, line by line:

  1. Run the left side.
  2. Read its value.
  3. If it is truthy, hand it back straight away. The right side is never touched.
  4. Otherwise, run the right side.
  5. Hand back the right side's value.
  6. The last line is a note, not a step.

&& has the same shape, but it hands back the left value when it is falsy. ?? hands back the left value unless it is null or undefined. In all three, the left side always runs, and the right side runs only when it is needed.

Optional chaining, written ?., skips even more. If the value before ?. is null or undefined, the whole rest of the chain is skipped: property reads, calls, and the arguments of those calls.

?. skips the whole call, arguments and allPop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
const cart = null;
const price = cart?.getPrice(say("argument", 3));
console.log(price);

Line 5 sets cart to null. Line 6 reaches cart?., sees null, and stops. getPrice is never looked up, and the argument never runs, so "argument" is never printed. price becomes undefined. Line 7 prints undefined, and that's the only line printed.

Line 6, cart?.getPrice(say(argument, 3)). The cart box holds null, so price becomes undefined. On the right, the argument card is never printed.cart?.getPrice(say("argument", 3))nullcartundefinedpriceargumentnever printed
cart is null, so ?. stops right there and price becomes undefined. The argument is skipped too, so "argument" is never printed.
How ?. runs: shortened from the specText
Let baseReference be ? Evaluation of the part before ?.
Let baseValue be ? GetValue(baseReference).
If baseValue is either undefined or null, return undefined.
Return ? ChainEvaluation of the rest of the chain.
// Shortened from the spec's steps for ?.

In plain words, line by line:

  1. Run the part before ?., here cart.
  2. Read its value: null.
  3. If it is null or undefined, hand back undefined straight away.
  4. Otherwise, run the rest of the chain, like .getPrice(…) with its arguments.
  5. The last line is a note, not a step.

Step 3 hands back before step 4, so nothing after ?. runs. That's different from cart.getPrice(…) without the question mark, which would read from null and throw a TypeError.

The same skipping works with assignment. ??= writes only when the current value is null or undefined. ||= writes only when it is falsy, and &&= only when it is truthy.

??= writes only when the value is missingPop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
const cart = { price: 3 };
cart.price ??= say("default", 5);
cart.note ??= say("note", "fresh");
console.log(cart.price, cart.note);

Line 5 makes a cart with a price of 3. Line 6 checks cart.price. It is 3, not missing, so the right side is skipped: "default" is never printed, and the price isn't written at all. Line 7 checks cart.note. It is undefined, so the right side runs, prints "note", and "fresh" is written. Line 8 prints 3 fresh.

The table below lists each operator that can skip work. Read each row as: "when I write this, the right side runs only in this case."

When the right side runs
When you writeWhen the right side runs
a || bOnly when a is falsy, such as 0, "", null, or undefined.
a && bOnly when a is truthy.
a ?? bOnly when a is null or undefined.
a ? b : cOnly one of b and c runs: b when a is truthy, c when it isn't.
a?.b(c)When a is null or undefined, nothing after ?. runs, not even c.
x ??= yOnly when x is null or undefined. Otherwise x isn't written at all.

Puzzles like a[i++] = i

Puzzles like a[i++] = i look like tricks, but the two rules you already know solve them: the left side runs first, and each part is finished before the next starts. They use two ways of adding 1 to a variable:

  • i++ adds 1 to i, but gives back the old value.
  • ++i adds 1 to i, and gives back the new value.
Write into a[i++]Pop out in the code editor (opens in a new tab)JavaScript
const a = [0, 0, 0];
let i = 0;
a[i++] = i;
console.log(a.join(", "));
console.log(i);

Line 1 makes an array a of three zeros. Line 2 sets i to 0.

Line 3 is the puzzle. The left side runs first: i++ gives the old value, 0, and sets i to 1. So the place to write is a[0]. Then the right side runs: i is now 1. So a[0] becomes 1. Line 4 prints 1, 0, 0, and line 5 prints 1.

Playground: four ways to write into a[i]
Write into the array, then print itPop out in the code editor (opens in a new tab)JavaScript
const a = [0, 0, 0];let i = 0;a[i++] = i;console.log(a.join(", "));console.log(i);
Console output1, 0, 0
index written0
value written1
i at the end1
Try it yourself
Left side first, then the right side.

The left side picked index 0. Then the right side gave 1. So a[0] became 1, and i ended at 1.

These are real JavaScript writes. Each button changes only line 3. Every run starts again from a = [0, 0, 0] and i = 0.

Try all four buttons. Each one changes only line 3, and each run starts again from three zeros and i = 0:

  • a[i++] = i: the index is 0, then i is 1, so a[0] becomes 1.
  • a[i] = i++: the index is 0, then i++ gives the old 0, so a[0] stays 0. Nothing seems to change.
  • a[++i] = i: ++i gives 1, so the index is 1, and a[1] becomes 1.
  • a[i] = ++i: the index is 0, then ++i gives 1, so a[0] becomes 1.

In every case, i ends at 1. Only the place and the value change, and both follow from "left side first".

x = x++ leaves x unchangedPop out in the code editor (opens in a new tab)JavaScript
let x = 3;
x = x++;
console.log(x);

Line 1 sets x to 3. Line 2 first finds the place to write, the variable x. Then the right side runs: x++ gives the old value, 3, and sets x to 4. Last, the = writes the 3 it was given into x, over the 4. Line 3 prints 3.

Two increments in one linePop out in the code editor (opens in a new tab)JavaScript
let i = 1;
const total = i++ + ++i;
console.log(total, i);

Line 1 sets i to 1. Line 2 runs the left side of + first: i++ gives 1 and sets i to 2. Then the right side: ++i sets i to 3 and gives 3. So total is 1 + 3 = 4. Line 3 prints 4 3.

Every engine gives the same answer

Some other languages, such as C, leave the order of some of these parts open, so a line like a[i++] = i can behave differently between compilers. JavaScript doesn't: the specification fixes the order, so every engine prints the same thing. Even so, in real code, put each change on its own line, so a reader doesn't have to solve a puzzle.

Reading input in the right order

In real code, the order matters most when a function reads the next item from somewhere: a list of numbers, a file, or a stream of messages. Each call moves a position forward, which is a side effect. So the call that runs first gets the first item.

Read the width, then the height, from a listPop out in the code editor (opens in a new tab)JavaScript
const input = [3, 4];
let position = 0;
function next() {
  const value = input[position];
  position = position + 1;
  return value;
}
const size = { width: next(), height: next() };
console.log(size.width, size.height);

Line 1 holds the input: 3, then 4. Line 2 sets position to 0. Lines 3 to 7 make next: line 4 reads the item at the current position, line 5 moves the position forward by 1, and line 6 hands back the item.

Line 8 builds an object. Its properties run from top to bottom, one at a time, so width calls next() first and gets 3. Then height calls next() and gets 4. Line 9 prints 3 4. If you swap the two properties, the values swap too.

Real-life analogyTickets at the deli counter

At a deli counter, you pull a numbered ticket from a machine. Each pull gives the next number. Who gets which number depends only on who pulled first.

In real life: The first person to pull a ticket gets the first number
In JavaScript: The first next() call gets the first item, 3
In real life: The second person gets the next number
In JavaScript: The second next() call gets 4
In real life: Swap who pulls first, and the tickets swap
In JavaScript: Swap width and height, and the values swap

When the order of calls matters, you can make it obvious: call next() on its own line and save the result in a variable, then build the object from those variables.

Does f() always run?
  • f() in price + f()
  • f() in add(1, f())
  • f() in { [f()]: 3 }
  • f() in name || f()
  • f() in cart?.show(f())
  • f() in cart.note ??= f()
Try it yourself
0 of 6 correct

Put each call in its group: calls that always run, and calls that may be skipped.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

Most mix-ups about evaluation order come from mixing up two questions: "which values belong together?" and "which part runs first?". Precedence answers the first. Evaluation order answers the second.

  • "Precedence decides which part runs first." No. Precedence only groups the parts. In a + b * c, a still runs first.
  • "An assignment runs its right side first." No. The left side runs first, to find the place to write. Only the write itself comes last.
  • "x += f() sees the changes f() makes to x." No. The old x is read before f() runs.
  • "Calling a missing method fails before its arguments run." No. The arguments run first. Then the "is it a function?" check throws a TypeError.
  • "?. only skips one property read." No. It skips the whole rest of the chain, including calls and their arguments.
  • "The order can change from one engine to another." No. The specification fixes it, and every engine follows it.

The table below puts the three ideas side by side. Read each row as: "this term means this, but it doesn't mean that."

Precedence, evaluation order, and short-circuiting
TermWhat it meansWhat it doesn't mean
PrecedenceWhich parts of an expression belong together.It doesn't decide which part runs first. Every operand still runs from left to right.
Evaluation orderWhich part of an expression runs first.It doesn't decide how the parts are grouped. That's the job of precedence and brackets.
Short-circuitingSkipping the right side when the left side already decides the answer.It doesn't run the right side "later". The skipped part never runs at all.

Practice exercises

Guess the output of each short program before you run it. For each one, ask: which part is on the left, and is any part skipped? Try a guess first, then use the hints if you get stuck.

Exercise 1 · Warm-upPredict three calls around a minus

Line 5 subtracts a product from a number. Write the four printed lines in order, separated by commas.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function say(label, value) {
  console.log(label);
  return value;
}
console.log(say("x", 5) - say("y", 3) * say("z", 2));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict a computed key

    A cart gets one property with a computed key. Write the three printed lines in order, separated by commas.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function say(label, value) {
      console.log(label);
      return value;
    }
    const cart = {
      [say("key", "tea")]: say("value", 3),
    };
    console.log(cart.tea);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticePredict a compound multiply

      bump() changes count and also gives back a number. What does the last line print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      let count = 1;
      function bump() {
        count = 100;
        return 2;
      }
      count *= bump();
      console.log(count);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeCount the calls that ran

        load() counts how many times it runs. Two logical assignments may or may not call it. What does the last line print?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        let calls = 0;
        function load() {
          calls = calls + 1;
          return "tea";
        }
        const cart = { item: "cake" };
        cart.item ||= load();
        cart.note ??= load();
        console.log(cart.item, cart.note, calls);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFix the swapped size

          This program should read the width first and the height second, but it prints 4 3. Fix line 8. What does the fixed program print?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const input = [3, 4];
          let position = 0;
          function next() {
            const value = input[position];
            position = position + 1;
            return value;
          }
          const size = { height: next(), width: next() };
          console.log(size.width, size.height);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeSolve a[i++] = i++

            Line 3 has two i++, one on each side of the =. What are the two printed lines?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const a = [0, 0, 0];
            let i = 0;
            a[i++] = i++;
            console.log(a.join(", "));
            console.log(i);

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask two things. Which part is on the left, so it runs first? And does an operator like || or ?. skip a part? Each answer explains why it's right or wrong.

              The evaluation order quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In a + b * c, the * is grouped before the +. Which operand runs first?

                Choose an answer to see the explanation.

              2. Question 2 of 7In what order do these power operands print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function say(label, value) {
                  console.log(label);
                  return value;
                }
                console.log(say("a", 2) ** say("b", 3) ** say("c", 2));

                Choose an answer to see the explanation.

              3. Question 3 of 7What does x = x++ leave in x?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                let x = 3;
                x = x++;
                console.log(x);

                Choose an answer to see the explanation.

              4. Question 4 of 7What does i++ + ++i give when i starts at 1?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                let i = 1;
                const total = i++ + ++i;
                console.log(total, i);

                Choose an answer to see the explanation.

              5. Question 5 of 7What does this optional call print when the cart is null?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function say(label, value) {
                  console.log(label);
                  return value;
                }
                const cart = null;
                const price = cart?.getPrice(say("argument", 3));
                console.log(price);

                Choose an answer to see the explanation.

              6. Question 6 of 7What does calling a missing method print here?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function say(label, value) {
                  console.log(label);
                  return value;
                }
                const tools = {};
                try {
                  tools.format(say("argument", 3));
                } catch (error) {
                  console.log(error.name);
                }

                Choose an answer to see the explanation.

              7. Question 7 of 7In getCart().price = getPrice(), which function runs first?

                Choose an answer to see the explanation.

              Key takeaways

              JavaScript never guesses the order. It runs the parts of every expression from left to right, and only a few operators may skip a part.

              • Evaluation order is the order in which JavaScript runs the parts of an expression: left to right, one part at a time.
              • Precedence and brackets decide which values are joined. They don't change which part runs first.
              • An assignment runs its left side first, to find the place to write, then its right side, and writes last.
              • x += f() reads the old x before f() runs, so changes made by f() are written over.
              • A call finds the function, then runs its arguments from left to right, and only then checks that the function can be called.
              • In an object, each computed key runs just before its value, one property at a time.
              • &&, ||, ??, ? : and ?. may skip a part completely. A skipped part never runs.

              Remember the one-liner.
              Grouping decides what is joined; the left side always runs first.

              Coming next: test262 & conformance. It shows how the specification's rules, like the ones in this lesson, are tested in every JavaScript engine.

              CompleteFrontend Clear concepts. Working examples.