Once your data is nested inside objects and arrays, the old way to dig a value out is verbose and error-prone: const first = list[0], const name = user.name, const role = user.role. Three lines, three chances to mistype a property, and nothing on the left side that mirrors the shape of the data on the right.
Destructuring fixes that by letting a single pattern describe the shape it wants, and the engine fills in the variables to match. It works on both objects (by key) and arrays (by position), and it scales from pulling two fields out of a config to reaching deep into a nested response. What used to be a wall of assignments becomes one line that reads almost like the data itself.
flowchart LR
P["{ name, role } pattern"] -->|"matches keys"| V["{ name: 'Ada', role: 'eng', ... }"]
V --> B["name = 'Ada',<br/>role = 'eng'"]
style P fill:#e0900b,color:#fff
style V fill:#3776ab,color:#fff
Object destructuring: pull by key
For objects, the pattern lists the keys you want, and variables of the same name receive the matching values. You can also rename on the way out with the key: newName form, and hand a default with = value for keys that might be missing:
const user = { name: "Ada", role: "engineer", country: "UK" };
const { name, role } = user; // name='Ada', role='engineer'
const { country: from } = user; // rename: from='UK'
const { age = 30 } = user; // default: age=30 (key absent)
Notice the pattern reaches in by name, not position — the order you write the keys does not matter, only that they match. A default kicks in only when the key is genuinely absent (its value would be undefined); a key holding null stays null, because null is a real value, not a missing one.
const user = { name: "Ada", role: "engineer", country: "UK" };
const { name, role } = user;
const { country: from } = user;
const { age = 30 } = user;
console.log(name);
console.log(role);
console.log(from);
console.log(age);
Array destructuring: pull by position
For arrays there are no keys to match — the pattern works by position, left to right. const [a, b, c] = arr puts the first element in a, the second in b, the third in c. To skip a slot you simply leave a gap with a comma: const [first, , third] = arr grabs the first and third and throws away the second.
The standout trick is the swap. Two variables trade places in a single statement with no temporary variable — [a, b] = [b, a] — because the right-hand array is built first, then unpacked into the left. What would be three lines with a temp becomes one, and it stays readable. The same positional idea lets you pull 'the rest' of an array into its own array with the rest pattern: [first, ...rest] = arr.
flowchart LR A["a=5, b=9"] -->|"[a,b] = [b,a]"| S["a=9, b=5"] style S fill:#1e7f3d,color:#fff
const [first, second, third] = [10, 20, 30]; console.log(first, third); const [a, , c] = [1, 2, 3]; // skip the middle console.log(a, c); let x = 5, y = 9; [x, y] = [y, x]; // swap without a temp console.log(x, y);
Nesting and defaults
Patterns compose, so you can destructure a value that is itself nested inside another object or array. Write const { user: { name } } = data to reach straight through data.user and pull out name — the colons trace the path inward, and only the leaf names (name) become variables; user is just a waypoint, not a binding.
Defaults stack with nesting too: every level of the pattern can offer a = fallback, so a deeply-buried field that is sometimes missing gets a sensible value instead of undefined. The rule is the same throughout — a default fills in only when the value at that spot is undefined, nothing more.
const data = { user: { name: "Grace", age: 40 } };
const { user: { name, age, title = "staff" } } = data;
console.log(name, age, title);
const { missing = "default" } = { present: 1 };
console.log(missing);
flowchart TD
Q{"value undefined?"} -- "yes" --> D["default applies"]
Q -- "no (even null)" --> K["value kept as-is"]
style D fill:#1e7f3d,color:#fff
style K fill:#e0900b,color:#fff
What does this print? The rename x: px pulls key x into a variable named px.
const point = { x: 3, y: 4 };
const { x: px, y: py } = point;
console.log(px, py);
x: pxreads keyxbut stores it in a variable calledpx.So px is 3 and py is 4; two arguments to console.log print space-separated.
Complete swap(a, b) so it reverses the two values with an array-destructuring swap — write the statement [a, b] = [b, a]; first (that is the line being drilled) — then return [a, b]. So swap(1, 2) returns [2, 1].
function swap(a, b) {
// swap a and b with one destructuring statement, then return [a, b]
}
Write
[a, b] = [b, a];— the right side is built first, then unpacked.After the swap,
return [a, b];.No temporary variable is needed.
function swap(a, b) {
[a, b] = [b, a];
return [a, b];
}
render() reads options.width, but calling it with no argument makes options undefined, so options.width throws TypeError. Fix it by destructuring the parameter with an empty-object default: change the signature to function render({ width = 100, height = 50 } = {}). Then render() returns 5000 instead of crashing.
function render(options) {
return options.width * options.height;
}
With no argument, options is undefined, so options.width throws.
Destructure in the parameter and default the whole thing to {}.
Give width and height their own defaults so a missing field still has a value.
function render({ width = 100, height = 50 } = {}) {
return width * height;
}
Write summary(response) that pulls the nested name and email out of response (whose shape is { user: { name, email } }) using nested object destructuring in the parameter, and returns the string "<name> <<email>>". Do not use dotted access like response.user.name. So summary({ user: { name: "Ada", email: "a@x.io" } }) returns "Ada <a@x.io>".
function summary(response) {
// destructure name and email from response.user in the parameter, then return the string
}
Destructure in the parameter:
({ user: { name, email } }).useris a waypoint, not a variable;nameandemailare the leaves.Build the result with a template literal:
${name} <${email}>.
function summary({ user: { name, email } }) {
return `${name} <${email}>`;
}
What is the value of w after const { w = 100 } = {}?
The empty object has no w, so the property is undefined, and the default = 100 applies. Defaults fill in only for undefined — never for null or other real values.
Recap
- Object destructuring pulls values out by key:
const { name, role } = user. Rename withkey: newName, default with= value. - Array destructuring pulls by position:
const [a, b] = arr. Skip a slot with a comma gap, gather the rest with...rest. - The swap
[a, b] = [b, a]trades two values in one statement, no temp. - Patterns nest:
const { user: { name } } = datareaches throughdata.user; waypoints are not bindings. - Defaults apply only for
undefined, and destructuringundefineditself throws — guard function parameters with= {}.
Next you'll convert these objects to and from JSON — the text format that carries them across the network and into storage.