skip to content

Expressions vs Statements

The difference between code that evaluates to something and code that just does something, and why the first composes. Interviewers use it to locate where mutable temporaries come from.

on this pageshow

questions

4

What makes a construct an expression rather than a statement, and why can only one of them nest inside another?

level: juniorimportance: must knowfreq 74%

answer

  1. two kinds of code shapes
  2. one leaves a value behind
  3. the other is run for effect
  4. value-denoting forms can nest
  5. statements can only be sequenced

basics

~20 s

An expression evaluates to a value, so it can sit anywhere a value is expected and nest inside a larger expression. A statement is run for its effect and denotes nothing, so nothing can be built around it.

solid answer

~50 s

An **expression** denotes a value: evaluate it and you are left with something the surrounding code can pass, bind, return or feed into a bigger expression. A **statement** is executed for its effect — it changes state or moves control — and leaves nothing behind, so an enclosing form has nothing to consume. That is why nesting, not syntax, is the real dividing line. In a grading routine, `record(band(normalise(raw)))` composes because every call denotes a value and can be dropped into the slot where a value is expected; a branch that merely assigns into a variable can only be *sequenced* before or after other statements. Languages differ in how much of their syntax denotes values — some treat a conditional or a whole block as value-denoting, others do not — but the property itself is always the same question: does this form leave a value behind?

code

pseudocode · 11 lines
pseudocode
// statement form: the branch only performs an effect
set band to nothing
if score >= 90 then
    set band to "A"
else
    set band to "B"
end
report(band)

// expression form: the branch denotes a value
report( if score >= 90 then "A" else "B" )

go deeper

for a junior

Be able to state it in one line — an expression evaluates to a value, a statement is run for its effect — and give one concrete example of each from code you have actually written.

for a middle

Go one step past the definition to the consequence: a value-denoting form can be nested, bound and returned, so it composes, while a statement can only take a position in a sequence.

for a senior

Show where the distinction changed a review decision: branches that existed only to write into a variable declared above them, and what the surrounding routine looked like after the choice denoted a value.

for a principal

Judge how far to push expression-orientation in a house style, given that some constructs in your stack simply are statements and forcing every one of them into a value costs more readability than it buys.

## Two kinds of code, separated by one property Every construct you write exists for one of two reasons. Either it **denotes a value** — evaluate it and what remains is something the surrounding code can use — or it is **executed for its effect**: it writes to a variable, transfers control, or touches the outside world, and leaves nothing behind that anything could consume. The first kind is an **expression**. The second is a **statement**. The property is not about length, punctuation or how clever the code looks. A ten-line arithmetic formula is an expression; a one-word jump out of a routine is a statement. The test is always the same: *after this runs, is there a value here?* If yes, the construct can be used as a part of something bigger. If no, it can only take its turn in a sequence. ## Why nesting is the real test Composition means putting a whole where a part is expected. An expression can appear in every one of those slots: - as an argument in a call; - as the right-hand side of a binding; - as the value a function hands back; - as an element inside a collection literal; - as an operand of another expression. A statement has exactly one legal position: the next slot in a sequence of statements. You cannot pass it, you cannot bind it to a name, and you cannot put it inside anything. That single restriction is the whole practical consequence of the distinction, and it is what an interviewer is listening for. A candidate who recites the two definitions has said the floor; a candidate who says "so the expression form can be nested and the statement form can only be sequenced" has answered the question. ## A grading rubric, in both shapes Consider a routine that turns a numeric score into a band. ```pseudocode // statement shape: the branch is run for its effect set band to nothing if score >= 90 then set band to "distinction" else set band to "pass" end record(band) // expression shape: the branch denotes a value record( if score >= 90 then "distinction" else "pass" ) ``` Both run the same test and record the same string. What differs is where the choice can live. In the first shape the branch produces nothing, so a name has to be created *in advance* for the branches to write into, and the call has to come after them. In the second the choice itself denotes a string, so it can be handed straight to the call — no name, no ordering constraint between the branch and the use. ## What follows from the difference | property | expression | statement | |---|---|---| | what it leaves behind | a value | nothing | | can nest inside another form | yes | no | | can be bound to a name directly | yes | no | | can be handed to a call | yes | no | | needs a place prepared for its result | no | yes, when it writes one | | composes into larger units | yes | only by sequencing | The row that pays for the rest is the last one. Because expressions compose, an expression-shaped routine can be read inside-out: you understand the innermost part, then the part that wraps it, and you never have to remember what happened earlier. A statement-shaped routine has to be read in order, carrying the current value of every name in your head, because any earlier statement may have changed one. ## Where the line actually sits Do not carry one language's line into an interview as if it were universal. Languages differ in how far the expression side reaches: some treat the two-way choice, the multi-way choice and even a block as value-denoting forms; others treat all three as statements and offer a separate small operator for the two-way case only; some treat assignment itself as denoting a value, which is a genuinely different design with its own hazards. What is *not* language-specific is the underlying question, and it is the one to answer at a whiteboard: does this form denote a value, or is it run for what it does? One more clarification that comes up constantly. The same syntax can appear on both sides of the line. A call that hands back a value is an expression; the very same call written on its own line, with the result ignored, is being used as a statement. The role, not the spelling, decides. ## The trap The trap is treating this as trivia. It is the root of the next question in the chain — why a routine has a variable declared empty at the top and assigned in every branch below — and that variable is the thing interviewers are actually hunting for. It exists only because the branch produced nothing to bind.

  • Is a call an expression or a statement?
    Either, depending on the role it is playing. A call that denotes a value and is used for that value is an expression; the same call written on its own line with its result ignored is being used as a statement. The syntax does not decide it — whether the surrounding code consumes a value does.
  • Why can a value-denoting conditional be passed straight to a call while a branching statement cannot?
    A call needs a value in each argument slot. The value-denoting conditional evaluates to one, so it fits the slot directly. The branching statement evaluates to nothing, so there is nothing to put in the slot; the only way to get its result into the call is to prepare a name first, have the branches write into it, and pass the name.
  • Does the distinction still matter in a language where almost everything denotes a value?
    Yes, and it shifts to a sharper question. When nearly every form denotes a value, the interesting thing about a form is no longer whether it can nest but what it also does while nesting. A construct that yields a value and writes to state can be dropped into an expression, and the write travels with it.

An expression is a number written on a slip you can hand to someone, who can add it into a larger total. A statement is flipping a switch: the room changes, but you have nothing in your hand to pass on.

saying these in an interview costs you the question

  • Says an expression is just a statement without the terminator.
  • Says every statement secretly returns a value that is discarded.
  • Thinks any short one-line construct counts as an expression.
  • Treats the distinction as syntax trivia with no consequence.
  • Assumes every language draws the line in the same place.
open as a page

A routine declares an empty band variable and assigns it in each branch below — what change removes that variable?

level: middleimportance: must knowfreq 58%

basics

~20 s

Make the choice itself denote a value and bind the result once: each arm yields a band instead of assigning one. The name is then initialised at its declaration, never reassigned, and no path can leave it empty.

open as a page

How does a block that evaluates to its last value keep a multi-step calculation out of the enclosing scope?

level: middleimportance: should knowfreq 44%

basics

~20 s

A block that denotes a value can be placed where the value goes, so the intermediate names used to compute it live and die inside it. The enclosing scope sees one bound result instead of the scratch names.

open as a page

When a construct both denotes a value and performs an effect, what does nesting it inside a larger expression cost?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

It costs the reader the ability to read the enclosing expression for its value alone. The nested form also acts, so the effect now happens wherever that sub-expression sits and only if the surrounding form evaluates it.

open as a page