Photo by Eric Er on Unsplash

On this page

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).

expr-vs-stmt.js
// Expression statement: value is discarded
42;
console.log((() => 1)()); // IIFE is an expression
// Statement: no usable value
if (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.

asi-trap.js
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.

strict.js
"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.

module.mjs
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).

blocks.js
{
var x = 1;
let y = 2;
}
console.log(x); // 1
// console.log(y); // ReferenceError

Comments 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.

Search articles

Type a word to search published articles.

Open the search page