← Fundamentals
Guided lesson 5 questions · 6 languages

Expressions and statements

Code that gives a value, code that does something, and code that does both.

An expression is code that gives a value, such as 3 + 4, a call, or a comparison. A statement is code that does something, such as declaring a name or choosing which lines run next; it gives no value. Each language draws the line in its own place. A value that nothing uses is lost, and an expression that also changes something can surprise you.

Before you begin

Why this matters

Many bugs are a value that was computed and then lost, or a value that is not the one you expected because the expression also changed something. When you can tell expressions from statements, you can see where each value goes.

What this helps you reason about

  • Spot a computed value that nothing uses.
  • Predict what condition ? a : b gives when it is part of a longer expression.
  • Predict the value of attempts++ and the value attempts holds after it.

In TypeScript, some code gives a value and some code only does something. This lesson separates the two: expressions, which give values, and statements, which do not. It shows the expression that chooses a value, an assignment used as a value, and expressions that change a variable while they give a value.

Already know this? The short version
  • An expression gives a value (jump + 12, a call, a comparison); a statement does something (a declaration, if, while). An expression on its own line is a statement whose value is thrown away, and TypeScript accepts jump + 12; without a word.
  • if is a statement; condition ? a : b is the expression that chooses a value. The false side reaches to the end of the expression, so put a ? : that is part of a sum in parentheses.
  • An assignment is an expression whose value is the value stored: a = b = 0 sets both, and if (x = 5) is accepted and always true.
  • x++ gives the old value and adds 1; ++x adds 1 and gives the new value. Operands are computed from left to right, so attempts = attempts++ leaves attempts unchanged.

Go to the final check· Go to the practice tickets

Words in this lesson (6)
expression
A piece of code that gives a value, such as 3 + 4, a call, or a comparison. An expression can be part of a larger expression.
statement
One instruction in a program, such as a declaration or a print. A semicolon ends it. A line end ends it too where the next line cannot continue it, and always after return.
expression statement
An expression written on its own as a statement, such as a call on its own line. Its value is thrown away.
conditional operator (? :)
An expression of the form condition ? a : b. It gives a when the condition is true and b when it is false, and computes only the side it gives.
side effect
A change that code makes besides giving its value, such as changing a variable or printing a line.
increment operator
x++ or ++x. Both add 1 to x. x++ gives the old value, and ++x gives the new value.

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 / Values

Which code gives a value?

Syntax and meaning called a piece of code that gives a value an expression, and one instruction a statement. This lesson looks at the difference. An expression gives a value: 640 + 72, a comparison such as jump > 700, or a call to a function that returns a value. Expressions nest: an expression can be one part of a larger one. It can be an operand, the value an operator works on, such as 640 in 640 + 72. It can also be an argument of a call, or the right side of a declaration.

A statement does something and gives no value. A declaration such as const best = 712; creates a name. if and while decide which statements run next. A statement cannot stand where a value is needed: const bonus = if (jump > 700) … is refused: Expression expected.

An expression can also stand on its own line as a statement, called an expression statement. Its value is then thrown away. That is right for console.log(…), which you call for what it does. It is a bug for jump + 12;, which computes 652 and throws it away. TypeScript accepts that line without an error or a warning, and jump still holds 640.

main.ts
let jump = 640;
jump + 12; // 652, thrown away
console.log(jump); // 640
jump = jump + 12;
console.log(jump); // 652

An expression gives a value; a statement does something. A value that nothing uses is lost.

Coming from Go: Go refuses jump + 12 on its own line as “is not used”. TypeScript accepts it and throws the value away.

Precisely: linters

The ECMAScript grammar allows any expression as a statement. Linters such as ESLint report one that has no effect (the rule no-unused-expressions); the TypeScript compiler does not.

Experiment

Throw a computed value away

jump + 12 is an expression: it gives 652. Written on its own line, it is an expression statement, and its value is thrown away. Only an assignment stores a value in jump.

Why it matters When a line computes a value, check that something uses it: a declaration, an assignment, an argument, or a return.

Predict the printed number. Then change line 2 to jump = jump + 12;, and predict again.

let jump = 640;
jump + 12;
console.log("jump:", jump);
What to look for

Before the edit, jump still shows 640 at line 3, the compiler reports nothing, and the run prints jump: 640. Node, which runs the program, stops on line 3 twice; that is not a second run. After the edit, jump shows 652 on line 3, and the run prints jump: 652.

Expected output

jump: 640

02 / Choosing

Can an if give a value?

An if statement chooses which block runs. It gives no value. When you want to choose a value, use the conditional operator: condition ? a : b. It is an expression. It gives a when the condition is true and b when it is false, and it computes only the side it gives.

The conditional operator has a lower precedence than almost every other operator. Everything after the : up to the end of the expression is the false side. So jump > 700 ? 10 : 0 + jump means jump > 700 ? 10 : (0 + jump). With a jump of 712 it gives 10, not 722. When a ? : is one part of a larger expression, put the ? : in parentheses.

One ? : can be the false side of another: a ? x : b ? y : z means a ? x : (b ? y : z), and b is tested only when a is false. Put each test on its own line, so the reader sees the order of the tests.

main.ts
const jump = 712;
const bonus = jump > 700 ? 10 : 0;
console.log(jump + bonus); // 722
console.log(jump > 700 ? 10 : 0 + jump); // 10

if is a statement; ? : is the expression that chooses a value. Inside a larger expression, put it in parentheses.

Coming from Rust: In Rust, if itself gives a value. In TypeScript it does not; ? : takes its place.

Experiment

Choose a value with ? :

Everything after the : up to the end of the expression is the false side. So jump > 700 ? 10 : 0 + jump means jump > 700 ? 10 : (0 + jump), and a true condition gives only 10.

Why it matters When ? : is one part of a larger expression, put the ? : in parentheses.

jump is 712. Predict the printed score. Then put parentheses around jump > 700 ? 10 : 0, so that line 2 reads const score = (jump > 700 ? 10 : 0) + jump;, and predict again.

const jump = 712;
const score = jump > 700 ? 10 : 0 + jump;
console.log("score:", score);
What to look for

Before the edit, step to line 3: score shows 10. The false side, 0 + jump, was never computed. The run prints score: 10. After the edit, score shows 722.

Expected output

score: 10

03 / Assignment

Is an assignment a value?

An assignment such as best = 712 is an expression in TypeScript. It stores the value, and its own value is the value stored. That allows a chain: best = last = 698 stores 698 in last, and then stores the value of that assignment, 698, in best.

It also allows a common mistake. if (best = 712) is accepted: the condition stores 712 in best, and 712 counts as true, so the block always runs. The comparison you meant is ===. Conditions and truthiness explains which values count as true.

main.ts
let best = 0;
let last = 0;
best = last = 698; // both 698
if (best = 712) {
  console.log(best); // 712, every time
}

An assignment is an expression whose value is the value stored. In a condition it is almost always a mistake for ===.

Precisely: asking for a warning

tsc accepts an assignment in a condition. ESLint reports it with the rule no-cond-assign.

04 / Side effects

What happens when an expression changes something?

Most expressions only compute a value. Some also change something: a call can print, and an assignment changes a variable. A change that code makes besides giving its value is a side effect.

The increment operators are expressions with a side effect. attempts++ adds 1 to attempts, and its value is the old value. ++attempts adds 1, and its value is the new value. On its own line, attempts++; is a statement, and both forms do the same thing.

Inside a larger expression the order matters. TypeScript computes the parts of an expression from left to right, and each side effect happens as soon as its part is computed. In attempts = attempts++, the right side gives 3 and stores 4 in attempts; then the assignment stores 3 again. The line changes nothing. Write attempts++; on its own line instead.

main.ts
let attempts = 3;
const shown = attempts++;
console.log(shown, attempts); // 3 4
attempts = attempts++;
console.log(attempts); // still 4

A side effect inside an expression happens while the expression is computed. Give each side effect its own statement.

Precisely: the order of calls

In first() + second(), first runs before second, and the arguments of a call are computed from left to right. The ECMAScript specification fixes this order.

Experiment

Assign a variable its own increment

The right side is computed first: attempts++ gives the old value, 3, and stores 4 in attempts. Then the assignment stores its value, 3, in attempts again. The 4 is overwritten before any stop can show it.

Why it matters Never assign the result of x++ back to x. Write x++; on its own line.

attempts is 3. Predict what attempts holds after line 2. Then change line 2 to attempts++;, and predict again.

let attempts = 3;
attempts = attempts++;
console.log("attempts:", attempts);
What to look for

Before the edit, attempts shows 3 at every stop from line 2 on, and the run prints attempts: 3. After the edit, attempts shows 4 on line 3, and the run prints attempts: 4.

Expected output

attempts: 3

05 / Try it

Can you explain a fresh example?

Predict the output before you reveal the answer. For each piece of code, ask: does it give a value, does it change something, or both?

main.ts
let tries = 2;
const label = tries++ === 2 ? "second" : "other";
let points = 0;
points = tries > 2 ? 30 : 0 + 5;
console.log(label, tries, points);

What does the program print?

Reveal the reasoning

second 3 30. tries++ === 2 compares the old value, 2, so label is "second", and tries is now 3. tries > 2 ? 30 : 0 + 5 means tries > 2 ? 30 : (0 + 5). 3 > 2 is true, so points is 30; the + 5 belongs to the false side.

The next lesson, Function calls, follows one call: when its arguments are computed, and how its result gets back.

Practice tickets

  1. EXPR-101 · Routes climbed on the first try get the wrong score → Free
  2. EXPR-102 · Boat names typed in small letters stay small on the club board → Sign in · Pro
  3. EXPR-103 · A faster later run gives a wrong best time → Sign in · Pro

Keep exploring

Function calls →

Primary references