Before you begin
Why this matters
Many bugs are not misspellings. The code is accepted, it runs, and it gives a wrong answer, because the grammar read it differently from you. If you read code the way the compiler does, you can see these bugs before you run anything.
What this helps you reason about
- Predict the value of an expression that mixes + and /.
- Spot a return whose value is on the next line, which gives undefined.
- Say which lines an if controls when the braces are missing.
A TypeScript program can be accepted by the compiler and still do something you did not mean. This lesson separates the two: the shape the language accepts, and what that shape does. It shows how operators group, where a statement ends, and which lines an if controls.
Already know this? The short version
- A syntax error, such as a missing ), stops the compiler before any line runs; accepted code can still mean something else.
- * and / bind before + and -, so a + b / 2 divides only b. / keeps the fraction. -2 ** 2 is a syntax error, and !x == y means (!x) == y.
- A line end ends a statement where the next line cannot continue it, and always after return: return alone on its line returns undefined.
- Without braces an if controls one statement, and an else belongs to the nearest if without an else, whatever the indentation. if (x); is refused: the body of an if cannot be the empty statement.
Words in this lesson (8)
- syntax
- The grammar of a programming language: which words and symbols may appear in code, and in which order.
- syntax error
- Text that breaks the grammar of the language, such as a missing closing parenthesis. The compiler reports it, and no line of the program runs.
- semantics
- The meaning of code: what it does when it runs. Code with correct syntax can still mean something you did not intend.
- 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).
- 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.
- automatic semicolon insertion
- The rule that adds a missing semicolon at a line end when the next line cannot continue the statement. After return, a line end always ends the statement.
- block
- A group of statements between braces, { … }. An if runs the block after its condition only when the condition is true.
- dangling else
- An else after two ifs written without braces. It belongs to the nearest if before it that has no else yet, whatever the indentation says.
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 / Syntax
What does the compiler accept?
Every programming language has a grammar: rules for which words and symbols may appear, and in which order. This grammar is called the syntax of the language. Text that breaks the grammar has a syntax error. As in From source to a running program, the compiler reports it before the run, and no line runs, not even the lines above the mistake.
Below, the last line has lost its closing parenthesis. The compiler reports Line 3: ')' expected. The message names what the grammar needed at that place. The real mistake can be a little earlier than the place reported, so read the line before it too.
What the accepted code does when it runs is its meaning, also called its semantics. The TypeScript compiler checks the syntax, the names, and the types. It cannot know what you intended, so it cannot check that the meaning is the one you wanted. The rest of this lesson is about code that is accepted and still does something else.
const hives = 4;
console.log("hives:", hives);
console.log("frames:", hives * 10;
// Line 3: ')' expected. Syntax is the shape the language accepts; a syntax error stops the program before any line runs.
Experiment
Break the grammar on purpose
The compiler reads the whole text against the grammar before anything runs. Text that does not fit the grammar cannot become a program at all, so no line runs.
Why it matters When a message says that something was expected, look at the place it names and just before it: a symbol is missing or extra there.
Predict the two output lines. Then delete the closing parenthesis just before the end of line 3, and predict: is the hives line still printed?
const hives = 4;
console.log("hives:", hives);
console.log("frames:", hives * 10);
What to look for
Before the edit, the run prints hives: 4 and frames: 40. After the edit, nothing runs, not even the first line, and the stepper shows the compiler’s message instead: Line 3: ')' expected. The grammar needed a ) before the line ended.
Expected output
hives: 4
frames: 4002 / Grouping
How does the grammar group an expression?
An expression is a piece of code that gives a value, such as hives * 10. When an expression has several operators, the grammar decides which one is applied first. This rule is called operator precedence. * and / come before + and -, as in school arithmetic. So full + half / 2 means full + (half / 2), whatever the spaces.
That can be exactly what you want. A beekeeper counts frames of honey: a full frame counts 1, and a half-full frame counts as half. full + half / 2 does that. An average is different: the average of a and b is (a + b) / 2, and without the parentheses only b is divided. In TypeScript, / always keeps the fraction.
Some groupings are refused. ** means "to the power of", and -2 ** 2 could mean (-2) ** 2, which is 4, or -(2 ** 2), which is -4. The minus sign here is a unary operator: an operator with one operand, the one value it works on. A unary expression is that operator with its operand, such as -2. TypeScript refuses a unary expression on the left of **. The message starts: An unary expression with the '-' operator is not allowed in the left-hand side of an exponentiation expression. Write the parentheses you mean.
Other groupings are accepted. ! means “not”, and it comes before ==. So !count == 0 means (!count) == 0. !count is a boolean, true or false, and 0 is a number. The compiler reports: This comparison appears to be unintentional because the types 'boolean' and 'number' have no overlap. “No overlap” means that no value belongs to both types, so the compiler treats the comparison as a mistake.
const full = 6;
const half = 3;
console.log(full + half / 2); // 7.5
console.log((full + half) / 2); // 4.5 Operators group by precedence, not by spacing. Add parentheses when you mean another grouping.
Coming from Python: Python accepts -2 ** 2 and gives -4. TypeScript refuses it: write (-2) ** 2 or -(2 ** 2).
Experiment
Predict which operator runs first
The grammar gives / a higher precedence than +, so full + half / 2 groups as full + (half / 2). Spaces do not change the grouping; parentheses do.
Why it matters When you divide a sum, such as an average, put the sum in parentheses.
Predict the printed number before running. Then put parentheses around full + half, so that line 3 reads const total = (full + half) / 2;, and predict again.
const full = 6;
const half = 3;
const total = full + half / 2;
console.log("frames of honey:", total);
What to look for
At line 4, total shows 7.5 in the variables panel, and the run prints frames of honey: 7.5. The stepper stops on line 4 twice; Node, which runs the program, often stops two times on one line. After the edit, total shows 4.5.
Expected output
frames of honey: 7.503 / Statement ends
Where does a statement end?
A statement is one instruction, such as const total = 6; or a call to console.log. In TypeScript a semicolon ends a statement. A line end usually does not: a statement can continue on the next line when the grammar can join the two lines.
When a semicolon is missing, one is added at a line end if the next line cannot continue the statement. This rule is called automatic semicolon insertion. One place is surprising: return. If return is alone on its line, a semicolon is inserted right after it. The function returns undefined, and the value on the next line is never computed. The compiler accepts this, and the call gives undefined, a value that means “no value”.
When a returned value is too long for one line, end the line with an operator such as +, which cannot end a statement, or open a parenthesis on the return line.
function label(hive: number) {
return
"hive " + hive + " is ready";
}
console.log(label(3)); // undefined A semicolon ends a statement. After return, a line end ends it too.
Coming from Java: In Java, return followed by a line end still returns the value on the next line, because only the semicolon ends the statement. In TypeScript, the same text returns undefined.
Precisely: unreachable code
Editors usually mark the line after return as unreachable code: a line that can never run. tsc reports it as an error only when the compiler setting allowUnreachableCode is false. The default only shows a suggestion in the editor, which is why the stepper runs the program.
Experiment
Move a returned value to the next line
A line that ends with + cannot be a complete statement, so the statement continues on the next line. return alone is a complete statement, so a semicolon is inserted after it, and the call returns undefined.
Why it matters When a function gives undefined where you expected a value, look first at the line of its return.
Predict what label(3) gives. Then move the whole value to the next line, so that line 2 is return on its own and line 3 is "hive " + hive + " is ready";, and predict again.
function label(hive: number) {
return "hive " + hive +
" is ready";
}
console.log(label(3));
What to look for
Before the edit, the stepper stops on line 2 and then on line 3, the second half of the same statement, and the run prints hive 3 is ready. After the edit, the stepper stops on line 2 twice and never on line 3. The run prints undefined, and there is no compile error.
Expected output
hive 3 is ready04 / Blocks
Which lines does an if control?
An if runs the block after its condition only when the condition is true. A block is a group of statements between braces, { … }. Without braces, an if controls exactly one statement: the next one. Indentation means nothing to the compiler. A line indented under the if, after that one statement, runs every time.
TypeScript refuses one form of this mistake. A semicolon right after the condition, as in if (count > 8);, would give the if a lone semicolon to control, a statement that does nothing. The compiler reports The body of an 'if' statement cannot be the empty statement.
else has the same rule. An else belongs to the nearest if before it that has no else yet, whatever the indentation. Below, the else is written under the first if, but it belongs to if (queenSeen). count is 6, so the first if is false, and nothing at all is printed. This is called a dangling else.
Braces show the compiler and the reader the grouping you mean. Write them even around one statement.
const count = 6;
const queenSeen = true;
if (count > 8)
if (queenSeen)
console.log("add a box");
else
console.log("wait");
// nothing is printed Braces, not indentation, decide which statements an if controls; an else belongs to the nearest if without an else.
05 / Try it
Can you explain a fresh example?
Predict the output before you reveal the answer. Read the code the way the compiler reads it: grouping first, then where each statement ends.
function boxes(hives: number) {
return (
hives / 2 + 1
);
}
console.log(boxes(8 + 2));
console.log(boxes(8) + 2); What are the two output lines?
Reveal the reasoning
6 and 7. The parenthesis opened on the return line keeps the statement going, so boxes returns hives / 2 + 1, which groups as (hives / 2) + 1. boxes(8 + 2) gets 10 and returns 6. boxes(8) + 2 calls boxes with 8, which returns 5, and then adds 2.
The next lesson, Variables, looks at the names that hold these values.
Practice tickets
- SYNTAX-101 · Compost heap averages come out higher than both thermometers → Free
- SYNTAX-102 · Dry seed trays get too little water → Sign in · Pro
- SYNTAX-103 · The greenhouse vent opens fully on cold days → Sign in · Pro