After the JavaScript engine glossary, this series covers everyday syntax and semantics—the surface rules engines parse before optimization kicks in.
Statements versus expressions
Most C-family languages keep a sharp line: if and for are statements; a + b is an expression. JavaScript allows expression statements (a function call or assignment as its own line) and, since ES2015, expression-oriented forms such as arrow functions and the conditional operator used as values.
An expression has a value; a statement performs an action. Function declarations are statements, but function expressions are expressions. That distinction matters for hoisting and for where you may place a function in another expression (covered in Functions in JavaScript).
// Expression statement: value is discarded42;console.log((() => 1)()); // IIFE is an expression
// Statement: no usable valueif (true) console.log("branch");Automatic semicolon insertion
JavaScript does not require semicolons. The parser inserts them (ASI) when a line break would otherwise create invalid syntax. Omitted semicolons are idiomatic in much of the ecosystem, but ASI fails when the next line can continue the previous statement.
function broken() { return { ok: true };}// ASI inserts ; after return → returns undefined
function fixed() { return { ok: true };}Lines starting with (, [, `, +, -, or / are common footguns after a value on the previous line—wrap the continuation or keep a semicolon before it.
Strict mode
"use strict"; at the top of a script or function opts into strict mode: assignments to undeclared bindings throw, delete on non-configurable properties throws, and with is forbidden. Modules and class bodies are strict by default without the pragma.
"use strict";// mistypedVar = 1; // ReferenceError in strict mode
function sloppy() { // non-strict unless nested in strict outer or "use strict" here}Strict mode also changes this in plain functions (see the functions chapter). Prefer modules or "use strict" in new code so behavior matches modern tooling and linters.
Scripts versus modules
Classic scripts share one global object (globalThis); top-level var and function declarations become properties on that object. ES modules (<script type="module"> or import in Node) get their own scope, strict mode, static import/export, and deferred execution.
export const version = 1;import { readFile } from "node:fs/promises";Modules can use top-level await where the host allows it; classic scripts cannot. Bundlers often emit modules even when the browser loads a single file—know which goal your file runs under when debugging globals and hoisting.
Blocks and lexical structure
{ } creates a block for if, loops, and standalone blocks. Unlike Java, a bare block does not introduce a new scope for var—only let and const are block-scoped (Variables and declarations).
{ var x = 1; let y = 2;}console.log(x); // 1// console.log(y); // ReferenceErrorComments and grammar gotchas
Line comments // and block comments /* */ behave as in other languages. Regular expression literals start with / and compete with division in expression context—another reason ASI and line breaks matter.
Template literals (backticks) are expressions and can span lines; they are not “strings with interpolation only” in the grammar—they are a distinct token type.
Comparing to other languages
| Habit | Java / C# | JavaScript |
|---|---|---|
| Statement terminators | Required ; |
Optional with ASI |
| Top-level scope | Package / namespace | Script global or module scope |
| Strictness | Always “strict” typing at compile time | Optional runtime strict mode + dynamic types |
| File as unit | Class per file common | Module or script; multiple exports per file |
Python readers: JavaScript has no significant whitespace for blocks—braces define blocks, and indentation is cosmetic.
Next: Variables and declarations.