Before you begin
Why this matters
Many surprising changes in a program come from two names that share one array or object. Knowing what an assignment copies lets you predict which changes spread, and make a copy when you need one.
What this helps you reason about
- Predict whether a change through one name shows through another.
- Make a copy of an array or object that later changes cannot reach.
- Spot a copy that still shares the arrays inside it.
Two names can start from the same value. Whether a change through one name shows through the other depends on what was copied: the value itself, or a reference to an object that both names share.
Already know this? The short version
- Assigning a primitive copies the value; assigning an object or array copies a reference, so both names are aliases of one object.
- === on two objects tells you whether they are one object (identity), which the debugger panel does not show.
- slice(), [...a], and { ...o } make shallow copies: a new outer array or object whose elements still lead to the same inner objects.
- structuredClone(value) makes a deep copy of plain data; copy as deep as the levels that some code will change.
Words in this lesson (6)
- primitive
- A simple value that is not an object: a number, string, boolean, bigint, symbol, undefined, or null. A primitive cannot be changed; operations on it make new values.
- 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.
- alias
- A second name for data that another name already reaches. A change made through one name is seen through the other.
- identity
- Whether two references lead to the same object. Two objects can hold equal contents and still be two objects.
- shallow copy
- A copy of the outer value only. Objects, lists, or pointers stored inside it are copied as references, so the copy shares them with the original.
- deep copy
- A copy of a value and of everything stored inside it, at every level, so that it shares nothing that can change with the original.
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 / Assignment
What does an assignment copy?
Variables showed that copying a number gives the second name its own value, while copying an object gives a second way to reach the same object. This lesson names that second way, and asks how far a copy reaches.
An assignment copies what the right side holds. For a primitive, such as the number 28, that is the whole value. For an array or an object, it is a reference. After const shared = books, the two names are aliases: two names for one array. A change made through shared is a change to that one array, so books shows it too.
The debugger panel shows the contents of both names, but not whether they are one array. shared === books answers that: on two objects, === is true only when both lead to the same object. This is called identity.
let pupils = 28;
let counted = pupils;
counted = 30; // pupils is still 28
const books = ["Matilda", "Holes"];
const shared = books;
shared[0] = "Wonder"; // books[0] is "Wonder" too
console.log(shared === books); // true Assigning an array or object copies a reference: the two names are aliases of one object.
Coming from Go: An array literal in TypeScript is not a fixed-size value like a Go array; assigning it never copies the elements.
Experiment
Change an array through a second name
let counted = pupils copies the number 28. const shared = books copies a reference, so shared and books are aliases of one array, and shared[0] = "Wonder" changes that array. slice() returns a new array, so after the edit shared has its own array.
Why it matters Before you change an array or object, know whether another name reaches it. If one might, change a copy.
Predict the three printed values. Then change const shared = books; to const shared = books.slice(); and predict again. slice() makes a new array with the same elements.
let pupils = 28;
let counted = pupils;
counted = 30;
const books = ["Matilda", "Holes"];
const shared = books;
shared[0] = "Wonder";
console.log(pupils, books[0], shared === books);
What to look for
Step past each line. counted changes to 30 while pupils stays 28. After shared[0] = "Wonder", both books and shared show ["Wonder", "Holes"], although the line wrote through shared. The panel does not show which names lead to one array; shared === books is the evidence. The run prints 28 Wonder true. After the edit, books keeps ["Matilda", "Holes"], shared alone shows "Wonder", and the run prints 28 Matilda false. A let or const that has not run yet shows as not initialized yet, and Step can stop on the last line twice; both are normal for this debugger.
Expected output
28 Wonder true02 / Copied or shared
Which values are copied whole, and which are shared?
Primitives are copied whole: numbers, strings, booleans, bigint, undefined, and null. None of them can be changed in place, so two names that hold the same primitive can never see a change through each other.
Everything else is an object: arrays, plain objects written with { … }, Maps, Sets, and functions. Every way of storing an object stores a reference. That includes an assignment, an argument passed to a function, an element of an array, and a property of another object. const shelf = [books] makes a new array whose one element leads to the same books array.
const books = ["Matilda", "Holes"];
const shelf = [books];
shelf[0][1] = "Wonder"; // books[1] is "Wonder" too
const plan = { list: books };
console.log(plan.list === books); // true Primitives are copied whole; every object, arrays included, is shared through references.
Precisely: function calls and const
A parameter receives its argument in the same way, so a function can change the caller’s array; Arguments, values, and references follows that case. const does not copy anything either: it stops one name from being assigned again, as Mutability and constants showed.
03 / Copies
How do you make an independent copy?
To get an array that later changes cannot reach, make a new array with the same elements. books.slice() does this, and so does [...books]: the spread ... writes the elements of books into a new array literal. For an object, { ...plan } makes a new object with the same properties.
The new array has its own identity. copy === books is false, and changing copy[0] leaves books as it was.
const books = ["Matilda", "Holes"];
const copy = books.slice();
copy[0] = "Wonder";
console.log(books[0], copy === books); // Matilda false slice(), [...a], and { ...o } make a new outer array or object.
Coming from Python: books.slice() and [...books] are what Python’s books[:] and list(books) are: new lists that hold the same elements.
04 / Depth
How deep does a copy go?
A copy made with slice or a spread is a shallow copy: the outer array is new, and its elements are copied the way an assignment copies them. An element that is an array or object is copied as a reference. So after copy = groups.slice(), copy[0] and groups[0] are one inner array.
A deep copy copies every level. structuredClone(groups) builds new inner arrays as well, so no change to the copy can reach groups. You can also copy level by level, for example each inner array in turn.
Choose the depth by what will change. A shallow copy is enough when no code changes the inner objects. When some code does, copy those levels too.
const groups = [["Ana", "Ben"], ["Cy"]];
const copy = groups.slice();
copy[0][0] = "Dee"; // groups[0][0] is "Dee" too
const deep = structuredClone(groups);
deep[1][0] = "Eva"; // groups[1][0] is still "Cy" A shallow copy shares everything inside it; copy as deep as the levels that will change.
Coming from React: A state update written as { ...state, list: state.list } keeps the old list: changing that list later changes the old state too, and React may not see a new value.
Precisely: structuredClone
structuredClone is built into Node.js 17 and later, and into browsers. It copies arrays, plain objects, Maps, Sets, and Dates; two references to one object become two references to one new copy; and it throws an error for a function. The older JSON.parse(JSON.stringify(value)) also makes a deep copy, but it drops undefined and turns Maps and Dates into other values.
Experiment
Copy an array of arrays one level deep
slice() copies the outer array. Its elements are references to the inner arrays, and they are copied as references, so both outer arrays lead to the same inner arrays. structuredClone copies every level.
Why it matters A copy made with slice() or a spread protects the outer array only. When code changes the inner arrays, copy them too.
Predict the three printed values. Then change groups.slice() to structuredClone(groups) and predict again.
const groups = [["Ana", "Ben"], ["Cy"]];
const copy = groups.slice();
copy[0][0] = "Dee";
console.log(groups[0][0], copy === groups, copy[0] === groups[0]);
What to look for
Step past copy[0][0] = "Dee". Both groups and copy now show [["Dee", "Ben"], ["Cy"]]. The run prints Dee false true: copy is a new outer array, but copy[0] and groups[0] are one inner array. After the edit, groups keeps "Ana", and the run prints Ana false false.
Expected output
Dee false true05 / Try it
Can you explain a fresh example?
Decide for each line whether it changes a copy or a shared object. The next lesson, Equality and comparison, compares identity with equal contents. Its link is at the end of the page.
const plan = { room: "B4", pupils: ["Ana", "Ben"] };
const copy = { ...plan };
copy.room = "C2";
copy.pupils[0] = "Eva";
console.log(plan.room, plan.pupils[0]);
What is printed?
Reveal the reasoning
B4 Eva. { ...plan } is a new object, so copy.room = "C2" changes the copy alone. But copy.pupils holds the same reference as plan.pupils: the spread made a shallow copy. Both names lead to one array, so the change to "Eva" shows through plan.
Practice tickets
- ALIAS-101 · The bonus screen always says that no mark went up → Free
- ALIAS-102 · Draft report cards always match the published ones → Sign in · Pro
- ALIAS-103 · Ticking homework for one pupil ticks it for the whole class → Sign in · Pro