Appearance
Values, Variables, and Everyday Syntax
JavaScript looks familiar enough that experienced programmers can read a surprising amount of it without formal study. That familiarity is useful, but it also encourages assumptions that are almost right. This article moves quickly through the syntax you need to write ordinary code and slows down where the runtime model matters.
Consider a small function that formats an order summary:
js
function summarizeOrder(order) {
const itemCount = order.items.length;
let subtotal = 0;
for (const item of order.items) {
subtotal += item.price * item.quantity;
}
const label = order.customer?.displayName ?? "Guest";
return `${label}: ${itemCount} items, $${subtotal.toFixed(2)}`;
}Nearly every line introduces an everyday JavaScript construct: a function declaration, const and let, property access, a loop, arithmetic, optional chaining, nullish defaulting, a method call, and a template literal. The syntax is not difficult. The decisions behind it are worth understanding.
Expressions produce values; statements control evaluation
An expression produces a value:
js
2 + 3;
order.items.length;
subtotal > 100;
formatPrice(subtotal);A statement performs an action in the program's grammar:
js
const subtotal = 20;
if (subtotal > 10) {
console.log("Large order");
}
return subtotal;The boundary is not perfectly visible from punctuation. A function call is an expression even if you use it as a statement because you ignore its result. An assignment is also an expression, though embedding assignments inside larger expressions is usually harder to read.
Blocks group statements and create lexical scope for let and const:
js
if (order.items.length === 0) {
const message = "The order is empty";
console.log(message);
}
// message is not available herePrefer const; use let when the binding changes
Modern JavaScript has three variable declaration forms. New code mostly uses two:
js
const currency = "USD";
let total = 0;const means the binding cannot be reassigned:
js
const currency = "USD";
currency = "EUR"; // TypeError: assignment to a constant variableIt does not make the referenced value immutable:
js
const settings = {
theme: "light",
};
settings.theme = "dark"; // allowed
settings = {}; // not allowedThe distinction is between the binding and the object:
text
settings ──constant binding──→ mutable objectUse const whenever the name will keep referring to the same value. Use let when the algorithm requires rebinding:
js
let total = 0;
for (const price of prices) {
total += price;
}This convention communicates more than “immutability.” It says whether a reader should expect the name to take on another value later.
You will encounter var in older code:
js
var status = "ready";var is scoped to a function rather than a block and has different initialization behavior. Those rules interact badly with closures and make local reasoning harder. There is little reason to choose it in new code, but later articles explain enough to read it safely.
JavaScript has seven primitive types
The primitive types are:
js
const title = "JavaScript"; // string
const count = 42; // number
const huge = 9_007_199_254_740_993n; // bigint
const enabled = true; // boolean
const identifier = Symbol("identifier"); // symbol
const missing = undefined; // undefined
const empty = null; // nullEverything else is an object, including arrays and functions in the broad sense of being reference values with properties. Functions have special call behavior, and typeof reports them as "function", but they participate in the object model.
Strings
Strings can use single quotes, double quotes, or backticks:
js
const first = "same text";
const second = 'same text';
const third = `same text`;A codebase normally chooses one quote style and delegates consistency to a formatter. Backticks create template literals, which can interpolate expressions and span lines:
js
const name = "Avery";
const unreadCount = 3;
const message = `${name} has ${unreadCount} unread messages`;Interpolation converts the expression result to a string. Be deliberate when interpolating objects; the default string is often unhelpful:
js
`${{ id: 1 }}`; // "[object Object]"Strings are immutable. Methods return new strings:
js
const raw = " hello ";
const clean = raw.trim();
console.log(raw); // " hello "
console.log(clean); // "hello"String indexing and length operate on UTF-16 code units, not user-perceived characters. Spreading a string handles Unicode code points better for many purposes, though grapheme clusters such as a letter plus a combining mark can still contain multiple code points:
js
"🙂".length; // 2
[..."🙂"].length; // 1This distinction becomes important when enforcing human-facing length limits.
Numbers
Most numeric values use one type, number, based on IEEE-754 double-precision floating point:
js
const integerLooking = 10;
const fractional = 10.5;
typeof integerLooking; // "number"
typeof fractional; // "number"There is no separate ordinary integer type. Number.isInteger checks whether a number's current value is integral:
js
Number.isInteger(10); // true
Number.isInteger(10.5); // falseFloating-point representation has familiar precision consequences:
js
0.1 + 0.2; // 0.30000000000000004Do not compare money calculations casually or round only when displaying without understanding the domain. Common strategies include storing an integer number of the smallest currency unit or using a decimal arithmetic library when requirements demand it.
JavaScript numbers also include special values:
js
1 / 0; // Infinity
-1 / 0; // -Infinity
0 / 0; // NaNNaN means “not a number” in the sense of an invalid numeric result, but its type is still number:
js
typeof NaN; // "number"
Number.isNaN(NaN); // trueIt is the only JavaScript value not equal to itself:
js
NaN === NaN; // falseUse Number.isNaN(value) rather than equality. Use Number.isFinite(value) when a boundary accepts only ordinary finite numeric values:
js
function isValidAmount(value) {
return typeof value === "number" && Number.isFinite(value);
}JavaScript also has positive and negative zero:
js
0 === -0; // true
Object.is(0, -0); // falseNegative zero rarely affects application logic, but it can appear through arithmetic and affect division:
js
1 / 0; // Infinity
1 / -0; // -InfinityBigInts
bigint represents integers outside the safe range of number:
js
const nextIdentifier = 9_007_199_254_740_993n;You cannot mix a bigint and a number in ordinary arithmetic:
js
1n + 1; // TypeError
1n + 1n; // 2nBigInts are useful for integer domains requiring exact large values, but they are not a drop-in decimal-money solution and are not supported directly by JSON serialization.
null and undefined
Both values commonly represent absence, but they arise through different conventions:
js
let value;
console.log(value); // undefined
const user = {
middleName: null,
};undefined often means a property, argument, or result was not supplied. null is often used when code deliberately records “no value.” APIs are not perfectly consistent, so boundary code must know the contract it is consuming.
JavaScript permits missing function arguments:
js
function inspect(value) {
console.log(value);
}
inspect(); // undefinedAn object property that does not exist also reads as undefined:
js
const user = { name: "Ada" };
user.nickname; // undefinedThat means a missing property and a property explicitly set to undefined often look the same through ordinary access. Later, Object.hasOwn will let you distinguish them when the difference matters.
Symbols
A symbol is a unique primitive value, often used as a collision-resistant property key or by language protocols:
js
const internalId = Symbol("internalId");
const record = {
[internalId]: 42,
name: "example",
};Every Symbol("internalId") call creates a different value. You will use symbols indirectly through protocols such as Symbol.iterator more often than you define application data with them.
Arrays and objects are reference values
An object literal creates a new object:
js
const first = { id: 1 };
const second = { id: 1 };
first === second; // falseThe objects have the same visible fields but different identities. Assigning an object copies the reference, not the object:
js
const original = { status: "draft" };
const alias = original;
alias.status = "published";
console.log(original.status); // "published"Arrays behave the same way:
js
const values = [1, 2];
const alias = values;
alias.push(3);
console.log(values); // [1, 2, 3]This is not a special parameter-passing mode. JavaScript passes values to functions. When the value is an object reference, the callee can use that reference to mutate the same object:
js
function markComplete(task) {
task.completed = true;
}
const task = { completed: false };
markComplete(task);
console.log(task.completed); // trueReassigning the parameter changes only the local binding:
js
function replaceTask(task) {
task = { completed: true };
}
const task = { completed: false };
replaceTask(task);
console.log(task.completed); // falseProperty access is ordinary expression syntax
Use dot notation for a statically known property name:
js
user.displayName;Use brackets for a computed key or a key that is not a valid identifier:
js
const field = "displayName";
user[field];
user["data-id"];Optional chaining stops when the value to its left is null or undefined:
js
const city = user.address?.city;Without it, reading .city from a missing address throws. With it, city becomes undefined.
Optional chaining does not perform validation:
js
const city = input.address?.city;
// city could still be a number, an empty string, or any other supplied value.It can also call an optional function:
js
logger.debug?.("Loaded configuration");This calls debug only if that property is non-nullish. If it exists but is not callable, the call still throws.
Operators follow familiar shapes with a few important rules
Arithmetic and comparison look conventional:
js
const subtotal = price * quantity;
const discounted = subtotal >= 100;
const remainder = count % pageSize;
const squared = value ** 2;Strict equality uses === and !==:
js
status === "ready";
count !== 0;Use those by default. The next article explains coercive equality and the few cases where seeing == does not automatically imply a bug.
Logical AND and OR short-circuit, but they return operands rather than converted booleans:
js
const selected = available && preferred;
const label = customLabel || "Default";Nullish coalescing is more precise when the question is whether a value is absent:
js
const retryCount = options.retryCount ?? 3;The conditional operator is an expression:
js
const label = isAdmin ? "Administrator" : "Member";Use it when both branches produce a compact value. Nested conditionals often become difficult to scan and are better expressed with statements or a helper function.
Control flow
JavaScript has familiar if, else, switch, while, and for statements:
js
if (amount < 0) {
throw new Error("Amount cannot be negative");
} else if (amount === 0) {
console.log("No charge");
} else {
console.log("Charge accepted");
}Braces are strongly preferred even for a single line because they keep edits safe and structure visible.
Use for...of to iterate values from an iterable such as an array, string, map, or set:
js
for (const transaction of transactions) {
console.log(transaction.id);
}for...in iterates enumerable property keys, not array values:
js
for (const index in transactions) {
console.log(index); // "0", "1", ... as string keys
}That makes for...in inappropriate for ordinary array traversal. It is occasionally useful for object-property enumeration, though Object.keys and related helpers often express that intent more clearly.
A traditional index loop remains useful when the index is part of the algorithm:
js
for (let index = 0; index < values.length; index += 1) {
console.log(index, values[index]);
}break exits the nearest loop, and continue skips to its next iteration.
Semicolons and automatic insertion
JavaScript can insert semicolons at certain line boundaries. Many codebases omit written semicolons consistently; many include them consistently. Both styles can work with an appropriate formatter and lint rules.
This course shows semicolons because they make statement boundaries explicit while learning. Automatic semicolon insertion is not simply “a semicolon appears at every newline,” and a few line starts can attach to the previous expression unexpectedly:
js
const value = getValue()
[1, 2, 3].forEach(log)Depending on the preceding expression, the bracket may be parsed as property access rather than the beginning of a new statement. A formatter and consistent style remove most practical risk. You need awareness, not a memorized catalog of parser edge cases.
A complete small function
Revisit the opening order summary with the semantics now visible:
js
function summarizeOrder(order) {
const itemCount = order.items.length;
let subtotal = 0;
for (const item of order.items) {
subtotal += item.price * item.quantity;
}
const label = order.customer?.displayName ?? "Guest";
return `${label}: ${itemCount} items, $${subtotal.toFixed(2)}`;
}orderis a local binding initialized with the argument value.itemCountnever needs another value, so it isconst.subtotalis rebound on each loop iteration, so it islet.- Each
itembinding is new for the iteration and never reassigned. - Optional chaining handles a missing customer without claiming the display name is valid.
- Nullish coalescing preserves an empty display name; whether that is desirable is a domain decision.
toFixedformats a number as a string and does not solve general decimal arithmetic.
The syntax is the surface. Reliable JavaScript comes from knowing the value model underneath it. The next article examines equality, conversion, truthiness, and absence in depth—the area where compact syntax most often answers a less precise question than the program intended.