Before you begin
Why this matters
This way of declaring names appears in almost every modern TypeScript file, and its bugs are quiet. Two names in the wrong order, a default that is not used, or a change made to a copy all compile and run without an error. When you can match each name with its part, you can see these bugs before the program runs.
What this helps you reason about
- Predict which part each name gets: by position from an array, by property name from an object.
- Spot a default that is not used, because the property holds null rather than undefined.
- Tell when changing a variable taken from an object changes the object, and when it does not.
One declaration can give several names their values at once, by taking an array or an object apart. This lesson shows how TypeScript matches each name with its part, what a name gets when its part is missing, and why changing the name later does not change the object it came from.
Already know this? The short version
- const [a, b] = pair takes array elements by position; const { band, seats } = gig takes properties by name. Each name is a new variable.
- Inside the brackets or braces, { band: act } renames, an empty place skips an element, and ...rest collects what is left into a new array or object.
- A function can return a tuple such as [number, number], and the caller takes it apart by position; [a, b] = [b, a] assigns several existing variables at once.
- A default, as in { support = "none" }, is used only when the part is undefined, not when it is null; use ?? to replace both. A missing array element gives undefined, and an array type such as number[] does not warn about it.
- Each name gets a copy of its part’s value. Assigning the name later leaves the object unchanged; if the part is an array or object, the copy is a reference, so a change made through it is seen in the original.
Words in this lesson (7)
- destructuring
- Taking an array or an object apart in a declaration or an assignment, such as const [a, b] = pair or const { seats } = gig, so that each name gets one part.
- object
- A value that groups named properties, written with braces: { value: 3 } is an object with one property, value.
- rest element
- A name written with ... in destructuring, such as ...others. It collects every remaining element of an array, or every remaining property of an object, into a new array or object.
- tuple type
- An array type with a fixed length and a type for each position, such as [number, number] for a pair of numbers.
- destructuring assignment
- An assignment such as [a, b] = [b, a], with names in brackets on the left. The right side is computed first; then its elements are assigned to the names, in order.
- undefined
- A special JavaScript value that means “no value yet”. A let variable declared without a value holds undefined.
- nullish coalescing (??)
- The operator a ?? b. It gives b only when a is undefined or null, so it keeps "" and 0. a || b gives b whenever a is falsy, including "" and 0.
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 / Several names
What does one line with several names do?
Variables showed a declaration that gives one name one value. TypeScript can also give several names their values in one declaration, by taking a value apart. This is called destructuring.
An array holds values in order, written in brackets, such as [45, 50]. An object holds values under property names, written in braces, as in Equality and comparison. On the left of =, the same brackets or braces describe the shape of the value, and each name inside them receives one part. This left side is called a pattern.
Below, band and seats get the properties of gig that have those names. first and second get the elements of halves, in order. All four are ordinary const variables.
const gig = { band: "Moon Trio", seats: 80 };
const halves = [45, 50];
const { band, seats } = gig;
const [first, second] = halves;
console.log(band, seats); // Moon Trio 80
console.log(first, second); // 45 50 A pattern on the left of = gives each name inside it one part of the value on the right.
Precisely: patterns in parameters
A parameter can be a pattern too. function label({ band, seats }: Gig) takes apart the object it is called with, when the call starts. The same rules apply as in a declaration.
Experiment
Take an object and an array apart
In { band, seats }, each name is also a property name, so band gets gig.band wherever it is written in the braces. In [first, second], the first name gets the element at position 0 and the second name the element at position 1, whatever the names are.
Why it matters For an object, the order of the names does not matter. For an array, the order is the only thing that matters.
Predict both lines. Then swap the two names inside the braces on line 3, and the two names inside the brackets on line 4, and predict again.
const gig = { band: "Moon Trio", seats: 80 };
const halves = [45, 50];
const { band, seats } = gig;
const [first, second] = halves;
console.log(band, seats);
console.log(first, second);
What to look for
At the stop on line 3, band and seats show not initialized yet. At the next stop, on line 4, both show their values: one line gave two variables their values. first and second appear together in the same way. The run prints Moon Trio 80 and 45 50. After the edit, the first line is the same and the second is 50 45. The stepper sometimes stops on the same line twice; that is normal for this debugger.
Expected output
Moon Trio 80
45 5002 / Parts
How does each name find its part?
In an array pattern, names take elements by position. To skip an element, leave its place empty: const [, second] = songs takes only the second element.
In an object pattern, a name takes the property with the same name, in any order. To use a different variable name, write the property name, a colon, and the new name: { band: act } puts gig.band in a variable called act.
A name written with ... collects everything that is left. It is called a rest element. In an array pattern, it gets a new array of the remaining elements; in an object pattern, a new object with the remaining properties. A rest element must come last.
const songs = ["Dawn", "Tide", "Coda"];
const [, second] = songs; // "Tide"
const [first, ...others] = songs; // "Dawn", ["Tide", "Coda"]
const gig = { band: "Moon Trio", seats: 80, city: "Lyon" };
const { band: act, ...details } = gig;
// act is "Moon Trio"; details is { seats: 80, city: "Lyon" } Array patterns match by position, object patterns by property name; a colon renames, and ... collects the rest.
Coming from Python: Python collects the rest with first, *others = songs. TypeScript writes [first, ...others], and the rest element must be the last name.
Precisely: patterns inside patterns
Patterns can be nested. const { songs: [firstSong] } = gig takes the songs property, then the first element of that array. Each level follows its own rule: by name in braces, by position in brackets.
03 / Results
How does one call give values to several names?
A function returns one value. To give back two numbers, it can return an array of two, and the caller takes that array apart. The return type [number, number] is a tuple type: an array with a fixed length and a type for each position.
Below, split turns a length in seconds into whole minutes and the seconds left. Math.floor rounds a number down to a whole number. The caller’s names get the parts by position, so their order must match the order in split’s return statement. The names inside split do not matter.
The same pattern works in an assignment to variables that already exist. Assignment and reassignment used [a, b] = [b, a] to swap two values: the whole right side is computed before anything is assigned.
function split(total: number): [number, number] {
const minutes = Math.floor(total / 60);
return [minutes, total - minutes * 60];
}
const [minutes, seconds] = split(200);
console.log(minutes + " min " + seconds + " s"); // 3 min 20 s A function can return a tuple. The caller’s names get its parts by position, so write them in the order the function returns them.
Coming from Go: A Go function can have several results. A TypeScript function returns one value, so it returns a tuple, and the caller takes it apart.
Precisely: returning an object instead
When two parts are easy to confuse, a function can return an object instead: return { minutes, seconds }, which is short for { minutes: minutes, seconds: seconds }. The caller then takes it apart by name, with const { minutes, seconds } = split(200), and the order no longer matters.
Experiment
Receive two numbers from one call
split returns one array, [3, 20]. The pattern on line 6 gives its first element to the first name and its second element to the second name. The minutes inside split and the minutes at the top level are separate variables; a matching name does not connect them.
Why it matters When a function returns several parts of the same type, only their order tells them apart. Check it against the function’s return statement.
Predict the printed line. Then swap the two names on line 6, so that it reads const [seconds, minutes] = split(200);, and predict again.
function split(total: number): [number, number] {
const minutes = Math.floor(total / 60);
return [minutes, total - minutes * 60];
}
const [minutes, seconds] = split(200);
console.log(minutes + " min " + seconds + " s");
What to look for
Step into split: its frame shows total = 200 and, after line 2, its own minutes = 3, while the top-level minutes and seconds still show not initialized yet. Back at the top level, on line 7, both have their values at once, and the run prints 3 min 20 s. After the edit, line 7 shows seconds = 3 and minutes = 20, and the run prints 20 min 3 s. The compiler says nothing: both parts are numbers.
Expected output
3 min 20 s04 / Missing parts
What happens when a part is missing or does not fit?
A pattern can name a part that is not there. An array pattern with more names than the array has elements gives the extra names undefined. So does an object pattern that names a missing property.
A default, written with = inside the pattern, gives a value to use instead. The default is used only when the part is undefined. null is a different value, which code writes on purpose to mean “no value”, and a default does not replace it. Below, support?: means the property may be missing, and string | null means it holds a string or null.
To replace both undefined and null, take the part without a default and use ??, which gives its right side for either.
type Gig = { band: string; support?: string | null };
const late: Gig = { band: "Reed Duo", support: null };
const { support = "none" } = late;
console.log(support); // null: the default is not used
const shown = late.support ?? "none";
console.log(shown); // none A default in a pattern replaces undefined only. Use ?? when null must be replaced too.
Coming from Python: In Python, a, b = [1, 2, 3] stops with an error. In TypeScript, extra elements are ignored and missing ones become undefined, with no error at run time.
Precisely: what the compiler checks
For a tuple type, TypeScript refuses a name past the end: with pair: [number, number], const [x, y, z] = pair fails with Tuple type '[number, number]' of length '2' has no element at index '2'. For an ordinary array type such as number[], it accepts any number of names and types each one as number, even when the element is missing and the value is undefined.
Experiment
Give a missing support act a default
support: earlySupport = "none" takes the support property, names it earlySupport, and gives it a default. early has no support property, so the pattern finds undefined and uses the default. late has the property, and its value is null. A default replaces undefined and nothing else, so lateSupport gets null.
Why it matters When data can hold null, a default in the pattern is not enough. Take the part, then replace null with ??.
Predict both lines. Then change support: null on line 4 to support: undefined, and predict again.
type Gig = { band: string; support?: string | null };
const early: Gig = { band: "Moon Trio" };
const late: Gig = { band: "Reed Duo", support: null };
const { support: earlySupport = "none" } = early;
const { support: lateSupport = "none" } = late;
console.log("early:", earlySupport);
console.log("late:", lateSupport);
What to look for
At the stop on line 7, earlySupport shows "none" and lateSupport shows null, and the panel lists late as {band: "Reed Duo", support: null}. The run prints early: none and late: null. After the edit, late shows support: undefined, lateSupport shows "none", and the run prints late: none.
Expected output
early: none
late: null05 / The original
What happens to the value you took apart?
A name taken from an object is a new variable. It starts with a copy of its part’s value, and after that it has no connection to the object. Assigning to it changes the variable only: below, seats becomes 78, and gig.seats stays 80.
A part that is an array or an object is different, for the reason Assignment and reassignment gave: the variable’s value is a reference to it. The variable and the property lead to the same array, so a change made through one is seen through the other. push, which adds an element at the end of an array, is such a change.
To change the object itself, assign to its property: gig.seats = gig.seats - 2.
const gig = { band: "Moon Trio", seats: 80, songs: ["Dawn", "Tide"] };
let { seats, songs } = gig;
seats = seats - 2; // gig.seats is still 80
songs.push("Coda"); // gig.songs has 3 songs too Assigning a destructured variable never changes the object; changing an array or object that it leads to does.
Experiment
Change a part you took out
seats got a copy of the number 80, so assigning seats changes nothing else. songs got a copy of a reference to gig’s array, so push changes the one array that both lead to. songs = ["Coda"] makes the variable lead to a new array and leaves gig’s array alone.
Why it matters To know whether a change reaches the original, ask whether you assigned the variable or changed what it leads to.
Predict both lines. Then replace songs.push("Coda"); with songs = ["Coda"]; and predict again.
const gig = { band: "Moon Trio", seats: 80, songs: ["Dawn", "Tide"] };
let { seats, songs } = gig;
seats = seats - 2;
songs.push("Coda");
console.log(gig.seats, seats);
console.log(gig.songs.length, songs.length);
What to look for
At the stop on line 4, seats shows 78, while gig still shows seats: 80. At the stop on line 5, songs and the songs inside gig both show ["Dawn", "Tide", "Coda"]. The run prints 80 78 and 3 3. After the edit, at line 5 songs shows ["Coda"] and gig still shows ["Dawn", "Tide"]; the run prints 80 78 and 2 1.
Expected output
80 78
3 306 / Try it
Can you explain a fresh example?
Before you predict, match each name with its part: by position, by name, or not at all.
type TourStop = { city: string; nights?: number; encores: number | null };
const tourStop: TourStop = { city: "Lyon", encores: null };
const { city: place, nights = 1, encores = 0 } = tourStop;
let [first, last] = ["Dawn", "Tide"];
[first, last] = [last, first];
console.log(place, nights, encores, first);
What is printed?
Reveal the reasoning
Lyon 1 null Tide. city: place renames, so place gets "Lyon". nights is missing, so its default 1 is used. encores holds null, which a default does not replace. The destructuring assignment computes [last, first] first, which is ["Tide", "Dawn"], and then gives first the value "Tide".
Practice tickets
- DESTR-101 · The seat finder sends ticket 47 to row 7 → Free
- DESTR-102 · The second booking starts from the old seat count → Sign in · Pro
- DESTR-103 · Short concerts get a broken first-and-last line → Sign in · Pro