Before you begin
Why this matters
Most surprises with variables come from one question: did this line change a value, or change which value a name refers to? Once you can tell those apart, you can predict what a program prints when two names are involved.
What this helps you reason about
- Predict whether changing one variable changes another.
- Tell reassignment (pointing a name at a new value) apart from mutation (changing an object that names share).
- Know what const protects and what it does not.
A variable is a name for a value. This lesson separates three things: the name, the value it holds now, and an object that several names can share.
Already know this? The short version
- let and const attach a name to a value. Assigning a number, string, or boolean copies the value; assigning an object copies a reference, so both names reach one object.
- Reassignment (x = …) changes which value one name holds. Mutation (x.prop = …) changes an object, and every name that refers to that object sees it.
- const forbids reassignment only. readonly is checked by the compiler only, and Object.freeze stops changes to an object’s own properties only, not to objects inside it.
- Types are checked before the program runs and then erased. A let declared without a value holds undefined, and reading a let before its declaration throws a ReferenceError.
- let and const are scoped to their block, and an inner declaration shadows the outer one. A value can still be used after its name goes out of scope, as long as a function or event listener can still reach it.
Words in this lesson (8)
- variable
- A name that holds a value. let and const declare variables; a let variable can be given a new value later, and a const variable cannot.
- type
- The kind of a value, such as number, string, or boolean. It decides which operations TypeScript allows on the value.
- compiler
- A program that checks your code and translates it into another form. The TypeScript compiler checks types, then removes them and produces plain JavaScript.
- undefined
- A special JavaScript value that means “no value yet”. A let variable declared without a value holds undefined.
- object
- A value that groups named properties, written with braces: { value: 3 } is an object with one property, value.
- reference
- A value that leads to an object. A variable whose value is an object holds a reference, not a copy of the object, so copying the variable gives a second way to reach the same object.
- mutation
- Changing the inside of an object, as in counter.value = 5. Every name that refers to that object sees the change. Reassignment (count = 5) is different: it changes only which value one name holds.
- scope
- The part of the program where a name can be used. For let and const, it is the nearest pair of braces, called a block.
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 / Names & values
What does a name refer to?
Declaring a variable attaches a name to a value. let count = 3 attaches the name count to the number 3. let next = count reads the value in count, 3, and attaches next to that value.
When count is assigned 5, only count changes. next still holds 3. The two names are not linked: next got a copy of the number when it was declared.
let count = 3;
let next = count;
count = 5;
console.log(count, next); // 5 3 Follow two names and their number values. These boxes are a picture of the values, not memory addresses.
A conceptual model, not a physical memory layout or live code execution.
1 / 3
Introduce count
- count
- 3
- next
- not declared yet
count holds 3. The next line has not run yet, so next does not exist.
Copying a number does not link the two names.
Experiment
Copy a number, then reassign it
let next = count reads the number in count, 3, and gives next its own copy. Assigning 5 to count later changes only count. The two names were never linked.
Why it matters A copied number stays as it was. If a value should follow later changes, you have to recompute it.
Predict what next holds before running. Then change count = 5 to count = 9 and check whether next follows it.
let count = 3;
let next = count;
count = 5;
console.log(count, next);
What to look for
Step past each line and watch the variables panel. count changes to 5; next stays 3. After the edit, count shows 9 and next still shows 3. Step sometimes stops on the same line twice; that is normal for this debugger.
Expected output
5 302 / Types
Where does the type come from?
Every value has a type, such as number or string. TypeScript works out the type of count from its first value, so count is a number. You can also write the type yourself. After that, TypeScript rejects assigning a value of another type.
TypeScript is JavaScript with types added. A compiler is a program that checks your code and translates it into another form. Before your program runs, the TypeScript compiler checks the types and then removes them. What runs is plain JavaScript, which is normal and expected.
let count: number = 3;
count = 5;
// count = "five"; // TypeScript rejects this line. TypeScript checks types before the program runs; they do not exist while it runs.
Coming from Java: A type assertion such as data as User is not checked when the program runs, while a Java cast is checked and can throw ClassCastException.
Precisely: types are erased
The compiler removes type annotations when it produces JavaScript. An annotation is not a check at run time: data from outside the program, such as JSON from a server, can still have the wrong shape.
03 / First value
What does a variable hold before you give it a value?
A variable can be declared before it has a useful value. A let with no value starts as undefined, a special value meaning “no value yet”. It is not zero.
Prefer giving a variable its value where you declare it. Then there is no moment when the name exists but holds nothing useful.
let pending: number | undefined;
console.log(pending); // undefined
pending = 3; A variable declared without a value holds undefined, not zero.
Precisely: the temporal dead zone
Reading a let variable before the line that declares it throws a ReferenceError. The time between entering its block and reaching the declaration is called the temporal dead zone. The older var keyword behaves differently.
04 / Change
What changes when you change a variable?
An object is a value that groups named properties. { value: 3 } is an object with one property, named value. counter.value reads that property.
There are two different ways to change a variable. Reassignment points a name at a new value: count = 5. Mutation changes the inside of an object: counter.value = 5.
Assigning an object does not copy it. The variable holds a reference: a way to reach the object. const alias = counter copies that reference, so alias and counter refer to the same object. Changing alias.value changes that one object, so counter.value shows the change too. const stops a name from being reassigned. It does not stop the object from being changed.
const counter = { value: 3 };
const alias = counter;
alias.value = 5;
console.log(counter.value); // 5 Reassignment changes a name; mutation changes an object that several names may share.
Coming from Python: This works the same way as Python: b = a never copies a list or dict, and both names reach one object.
Coming from Java: const is like final on a Java variable: neither one makes the object unchangeable.
Coming from React: Changing a state object and passing that same object to setState does not re-render: React compares the old and new values with Object.is, and they are still the same object. Pass a new object instead.
Precisely: readonly and Object.freeze
A readonly property stops assignment through that type, but only while the code is checked; it adds nothing at run time. Object.freeze stops changes at run time, but only to the object’s own properties, not to objects inside it.
Experiment
Change a shared object
const alias = counter does not copy the object. Both names now refer to one object, so changing alias.value changes the value that counter.value reads.
Why it matters const stops a name from being reassigned. It does not stop the object from being changed through any name that refers to it.
Predict what counter.value prints. Then change the second line to const alias = { value: 3 }; and predict again.
const counter = { value: 3 };
const alias = counter;
alias.value = 5;
console.log(counter.value);
What to look for
Step past alias.value = 5 and look at both counter and alias. Both show {value: 5}, although the line wrote through alias. The panel does not show which names reach the same object; the printed counter.value is the evidence. After the edit, alias is a second object: alias shows {value: 5}, counter keeps {value: 3}, and the run prints 3.
Expected output
505 / Scope
Where can a name be used?
A variable’s scope is the part of the program where its name can be used. let and const belong to the nearest pair of braces, called a block.
Declaring count again inside a block creates a second, separate variable. The inner count hides the outer one until the block ends. This is called shadowing. The outer count is never changed.
let count = 3;
{
let count = 5;
console.log(count); // 5
}
console.log(count); // 3 Shadowing creates a new variable; it does not change the outer one.
Coming from Python: A let declared inside an if or for block cannot be used after the block, while a Python variable assigned there belongs to the whole function.
Experiment
Hide a name in an inner block
let count inside the braces creates a second variable that exists only in that block. It hides the outer count until the block ends. Without let, the line would assign to the outer count instead.
Why it matters When a variable seems never to change, check whether an inner declaration created a different variable with the same name.
Predict both lines. Then remove the declaration keyword inside the block, so the inner line assigns instead of declaring, and predict again.
let count = 3;
{
let count = 5;
console.log("inner", count);
}
console.log("outer", count);
What to look for
Step into the block and watch count. Inside the block, count shows 5: the panel lists the inner count, which hides the outer one. After the block, count shows 3 again. After the edit, the inner line assigns to the outer count, count still shows 5 after the block, and the run prints inner 5 and outer 5.
Expected output
inner 5
outer 306 / How long
Does a value end when its name does?
A name and its value can last for different lengths of time. Here, makeReader creates an object and returns a small function, written () => counter.value, that reads the object. After makeReader returns, the name counter cannot be used. The object is still in use, because the returned function refers to it.
An event listener that refers to an object keeps that object in use in the same way, until the listener is removed.
Where values are stored, and when their memory is released, is the subject of Stack and heap in the Runtimes module.
function makeReader() {
const counter = { value: 3 };
return () => counter.value;
}
const read = makeReader();
console.log(read()); // 3 When a name goes out of scope, its value can still be in use.
Precisely: no fixed storage
JavaScript specifies behavior, not storage locations. The engine is the program that runs JavaScript, such as V8 in Node.js. It may keep a value in a processor register, store it with a function that uses it, or remove it. Memory is reclaimed some time after nothing the program can still reach refers to the value.
07 / Try it
Can you explain a fresh example?
Predict the final values before revealing the explanation. Track reassignment separately from mutation.
let first = { value: 3 };
const second = first;
first = { value: 5 };
second.value = 8;
console.log(first.value, second.value); What is printed, and which objects are shared at the end?
Reveal the reasoning
5 8. At first, both names refer to one object. Reassigning first points it at a new object. second still refers to the original object, which is then changed to hold 8.
Practice tickets
- VAL-101 · Keep the original measurement → Free
- VAL-102 · Propose seats without moving anyone → Sign in · Pro
- VAL-103 · Duplicate a print job → Sign in · Pro