Grammar, Types & Declarations
let and const replaced var for good reasons. This chapter shows exactly what changed, including hoisting and the "temporal dead zone", and then tours JavaScript's eight data types and their literals.
You will learn
varvsletvsconst: scope, re-declaration, and what "constant" really means.- Hoisting, and the temporal dead zone (TDZ) of
let,constandclass. - The seven primitive types plus objects, and the quirks of
typeof. - Primitives vs references: what gets copied and what gets shared.
- Every kind of literal, including
BigIntand numeric separators.
A few grammar basics
- JavaScript is case-sensitive and uses Unicode:
totalandTotalare different names, andprix_β¬is a valid identifier. - Comments are
// lineand/* block */. A#!/usr/bin/env nodeshebang is allowed as the very first line. - Statements end with
;. Automatic semicolon insertion (ASI) adds missing ones, but it has traps, so most style guides either always write semicolons or use a formatter.
β ASI trap: returns undefined
β Keep the value on the same line
Declarations: var, let, const ES2015
var | let | const | |
|---|---|---|---|
| Scope | function (or global) | block { β¦ } | block { β¦ } |
| Re-declare in the same scope | allowed | SyntaxError | SyntaxError |
| Re-assign | yes | yes | no (TypeError) |
| Use before the declaration | undefined (hoisted) | ReferenceError (TDZ) | ReferenceError (TDZ) |
| Creates a global property (in a classic script) | yes | no | no |
const means "constant binding", not "frozen value"
const stops you from re-assigning the name. If the value is an object, you can still change what's inside it. To make the object itself unchangeable, use Object.freeze, which is shallow: nested objects stay mutable.
(The assignment to a frozen property was silently ignored, because the REPL runs in sloppy mode. In a module it would throw a TypeError.)
π‘ Default to const, use let when you really re-assign, and don't use var in new code. Seeing let then tells the reader "this changes later".
β Java: const is like final on a reference: final List<String> l can still be added to. JavaScript variables have no declared type; the value carries the type.
Hoisting and the temporal dead zone
Before running a scope, the engine registers every declaration in it. This is called hoisting. What a name holds before its declaration line runs depends on how it was declared:
var: the name exists and isundefined.functiondeclarations: fully hoisted, so you can call them before the line that defines them.let,const,class: the name exists, but touching it throws aReferenceErroruntil the declaration runs. That gap is the temporal dead zone (TDZ).
β οΈ Pitfall: function expressions are not fully hoisted. const f = () => β¦ is a const, so calling f() above that line throws. Only function name() {} declarations can be called early.
The TDZ is about time, not position: a function can mention a let variable declared below it, as long as the function is called after the declaration has run.
Data types
JavaScript has seven primitive types and objects. A primitive is an immutable value with no methods of its own. When you call "abc".toUpperCase(), JavaScript briefly wraps the string in a String object to find the method.
| Type | Example | typeof |
|---|---|---|
| Undefined | undefined | "undefined" |
| Null | null | "object" β οΈ (a historic bug) |
| Boolean | true | "boolean" |
| Number (64-bit float) | 42, 3.14, NaN, Infinity | "number" |
| BigInt ES2020 | 9007199254740993n | "bigint" |
| String | "Dune" | "string" |
| Symbol ES2015 | Symbol("id") | "symbol" |
| Object (incl. arrays, functions, dates, β¦) | {}, [], () => {} | "object", or "function" for functions |
π‘ Reliable type checks: x === null for null, Array.isArray(x) for arrays, Number.isNaN(x) for NaN, and x instanceof Date for class instances. typeof is best for primitives and functions.
undefined vs null
Both mean "no value", by convention in different ways. undefined is what the language gives you (a missing property, a missing argument, a function with no return). null is what programmers write on purpose to say "intentionally empty". Many codebases check for both with x == null, the one widely accepted use of ==, or with x ?? fallback (Chapter 5).
Symbols, in one paragraph
Every Symbol() call creates a unique value, so it can be used as an object key that never collides with string keys. The built-in "well-known" symbols such as Symbol.iterator let you hook into language features (Chapters 13 and 15).
Primitives vs references
Assigning a primitive copies the value. Assigning an object copies a reference to the same object, just as in Java.
Comparing objects with === compares identity: {} === {} is false. Chapter 5 covers equality in depth.
Literals
| Kind | Examples | Notes |
|---|---|---|
| Numbers | 255, 0xff, 0o377 ES2015, 0b1111_1111, 2.5e3 | _ numeric separators ES2021 |
| BigInt | 10n, 2n ** 64n | arbitrary size integers; can't mix with Number |
| Strings | 'a', "b", `c ${x}` ES2015 | Chapter 6 |
| Arrays | [1, 2, 3], [1, , 3] | the second has a hole (Chapter 8) |
| Objects | { title, [key]: v, f() {} } | shorthand and computed keys ES2015 |
| Regular expressions | /\d+/g | Chapter 7 |
| Booleans, null | true, false, null | undefined is a global property, not a literal |
More examples
1. Choosing the declaration
2. A type-describing helper
The order of the checks matters: null and arrays must be handled before falling back to typeof, which would call both of them "object".
3. Shadowing with block scope
4. Large IDs need BigInt
5. Freezing a settings object deeply
Recap
- Use
constby default andletwhen you re-assign.varis function-scoped, re-declarable and hoisted asundefined. let,constandclassare in the TDZ until their line runs. Function declarations are fully hoisted.constfreezes the binding, not the value.Object.freezeis shallow.- There are seven primitives plus objects.
typeof null === "object", so useArray.isArrayandNumber.isNaNfor those checks. - Primitives copy by value and objects by reference.
structuredClonemakes a deep copy.
Exercises
Exercise 1 π’ easy predict the output
What do these give? typeof null, typeof NaN, typeof [], typeof 10n, typeof undefined
Show solution
"object", "number", "object", "bigint", "undefined". The first one is a bug from 1995 that can never be fixed without breaking the web.
Exercise 2 π’ easy fix the bug
This throws TypeError: Assignment to constant variable. Fix it.
Show solution
count is re-assigned, so it must be let. const book in forβ¦of is fine, because each iteration gets a fresh binding.
Exercise 3 π’ easy explain why
Why does describe in "More examples" #2 check x === null and Array.isArray(x) before using typeof?
Show solution
Because typeof null and typeof [] are both "object", so typeof can't tell them apart from plain objects. The special cases have to run first; everything else is described well by typeof.
Exercise 4 π’ easy predict the output
What does this print?
Show solution
number undefined. var i belongs to the surrounding scope and is 3 after the loop; let j only exists inside the loop.
Exercise 5 π‘ medium predict the output
What does each line print, or throw?
Show solution
The first prints undefined (a hoisted var). The second throws ReferenceError: Cannot access 'b' before initialization. Even typeof isn't safe inside the TDZ, although it is safe for names that were never declared at all.
Exercise 6 π‘ medium refactor it
Replace every var with let or const, choosing the most restrictive one that works.
Show solution
Only total and i are re-assigned. p is a new binding on each pass, so it can be const.
Exercise 7 π‘ medium build it Β· playground
Create playground/types.js with typeName(x), which returns one of null, array, nan, integer, float, bigint, string, boolean, symbol, function, undefined or object. Print it for a mixed sample.
Show solution
The special cases are checked first, and typeof handles the rest. Number.isInteger(3.0) is true, because JavaScript has a single number type.
Exercise 8 π‘ medium fix the bug
Changing draft also changes original. Fix it, so that nested arrays are copied too.
Show solution
Spread copies only the top level, so draft.tags is the same array object. structuredClone copies all the way down.
Exercise 9 π΄ hard predict the output
What does this print?
Show solution
ReferenceError. The inner let x is hoisted to the top of f's scope, so x inside f refers to the inner variable, which is still in its TDZ. It does not fall back to the outer "outer".
Exercise 10 π΄ hard mini-project Β· playground
Create playground/inventory.js. It holds a frozen CATALOG of books (id, title, price in cents) and a mutable stock object. Write restock(id, qty), which validates that qty is a positive safe integer and that the id exists, and a report() that prints each book with its stock. Show that the catalog can't be modified.
Show solution
This combines const with a deep freeze (each book frozen too), strict-mode errors on frozen writes, safe-integer validation, and destructuring in forβ¦of (Chapter 5).