Destructuring is how you pull values out of an object or array and bind them to variables in one statement, instead of reading them one property at a time. The two basic forms are object destructuring with braces and array destructuring with brackets:
const user = { name: "Ada", role: "admin" };
const { name, role } = user; // name = "Ada", role = "admin"
const point = [10, 20];
const [x, y] = point; // x = 10, y = 20That is the whole idea. The rest of this page is the detail that the one-liner versions skip: skipping elements, gathering a rest, renaming, defaults, nested shapes, and destructuring function arguments, which is where the two gotchas that bite everyone live. Destructuring has been stable since ES2015 (ES6) and nothing about the syntax has changed since, so everything here works in every current browser and in Node.
Object destructuring
For objects, the variable name has to match the property key. The braces on the left are not an object literal; they are a destructuring pattern that says "find these keys and bind them."
const config = { host: "localhost", port: 5432, ssl: false };
const { host, port } = config; // host = "localhost", port = 5432Order does not matter (you are matching by name), and you can pull as many or as few keys as you want. A key that does not exist on the object yields undefined, not an error.
Array destructuring
For arrays, you match by position, not name, so the variable names are yours to choose:
const rgb = [255, 128, 0];
const [red, green, blue] = rgb; // red = 255, green = 128, blue = 0This is also the cleanest way to swap two variables with no temporary:
let a = 1, b = 2;
[a, b] = [b, a]; // a = 2, b = 1Skipping elements
Leave a hole in the array pattern to skip a position. The commas still count:
const parts = ["2026", "06", "01"];
const [, , day] = parts; // day = "01"The rest pattern
A trailing ...rest gathers everything you did not name. With arrays it collects the remaining elements into a new array; with objects it collects the remaining own enumerable properties into a new object.
const [first, ...others] = [1, 2, 3, 4];
// first = 1, others = [2, 3, 4]
const { id, ...fields } = { id: 7, name: "Ada", role: "admin" };
// id = 7, fields = { name: "Ada", role: "admin" }The object-rest form is the idiomatic way to grab one property and keep "everything else" as a clean object, for example to strip an id before sending a payload. The rest element must come last; putting anything after it is a syntax error.
Aliases: renaming while you destructure
When the property key is not the variable name you want (a collision, a clearer local name, or just an ugly API key), rename it with key: localName:
const response = { user_id: 42, created_at: "2026-06-01" };
const { user_id: userId, created_at: createdAt } = response;
// userId = 42, createdAt = "2026-06-01"Read the colon as "bind this key to this variable." After that line, user_id is not a variable in scope (only userId is). This is the single most common point of confusion: { user_id: userId } does not create a user_id binding.
Default values
Give any destructured target a default with =. The default is used only when the value is undefined (a missing key or a missing array slot). A present value of null, 0, or "" is kept as is, because only undefined triggers the default.
const { timeout = 5000, retries = 3 } = { timeout: 1000 };
// timeout = 1000 (present, kept), retries = 3 (missing, defaulted)
const [primary = "main", secondary = "backup"] = ["db1"];
// primary = "db1", secondary = "backup"Alias and default together
You can rename and supply a default in one go. The order is key: localName = default:
const { color: themeColor = "blue" } = {};
// themeColor = "blue"The default applies to the renamed binding, so themeColor gets "blue" when color is absent.
Nested destructuring (and the path gotcha)
Patterns nest the same way the data does. To reach a value two levels deep, mirror the shape:
const order = { customer: { address: { city: "Berlin" } } };
const { customer: { address: { city } } } = order;
// city = "Berlin"Here is the gotcha that catches people: in that pattern, customer and address are not variables. They are path segments telling the engine where to walk. The only binding created is city. If you actually wanted a customer variable too, you have to name it separately:
const { customer, customer: { address: { city } } } = order;
// now both `customer` (the object) and `city` (the string) existThe nested form is also a footgun when an intermediate level can be missing: const { a: { b } } = {} throws TypeError: Cannot destructure property 'b' of 'undefined', because there is no a to read b from. Default the intermediate level (const { a: { b } = {} } = obj) or read it with optional chaining instead when the shape is not guaranteed.
Destructuring function arguments (the = {} gotcha)
Destructuring an options object right in the parameter list is one of the best uses of the feature. It self-documents which options a function accepts, and pairs naturally with defaults:
function te_connect({ host = "localhost", port = 5432, ssl = false }) {
return `${host}:${port}${ssl ? " (ssl)" : ""}`;
}
te_connect({ port: 6432 }); // "localhost:6432"That works as long as the caller passes an object. But call it with nothing, te_connect(), and it throws: you cannot destructure undefined. The fix is to default the whole parameter to an empty object so there is always something to destructure:
function te_connect({ host = "localhost", port = 5432, ssl = false } = {}) {
return `${host}:${port}${ssl ? " (ssl)" : ""}`;
}
te_connect(); // "localhost:5432", no throwThe trailing = {} is not optional decoration. Without it, te_connect() and any call that omits the argument crash. Make the default-empty-object a reflex on any function that destructures its parameter, because the call site that omits the argument is exactly the one you forget to test.
A related guideline worth keeping: once a function needs more than about three arguments, switch from positional parameters to a single destructured options object. It reads better at the call site (named, order-independent) and it grows without breaking existing callers.
Destructuring in loops and iterables
Destructuring shines in for...of over entries. Iterate an array of pairs, a Map, or Object.entries(...) and destructure each step:
const scores = [["Ada", 90], ["Linus", 85]];
for (const [name, score] of scores) {
console.log(`${name}: ${score}`);
}
const settings = new Map([["theme", "dark"], ["lang", "en"]]);
for (const [key, value] of settings) {
console.log(key, value);
}Map.prototype.entries() (and a Map directly, since it is iterable) yields [key, value] pairs, so the [key, value] pattern lines up exactly. The same works in array callbacks: arr.map(([k, v]) => ...) and arr.forEach(([k, v]) => ...) destructure each element inline.
FAQ
See also
- Modern JavaScript array methods:
at,flat,toReversed, and the mutation trap, the methods you reach for once you have destructured an array. - JavaScript object methods:
keys,values,entries, andfromEntries, which pair directly with thefor...ofdestructuring above. - Merge and clone objects in JavaScript: spread,
Object.assign, andstructuredClone, the same brace syntax doing a different job.
Sources
Authoritative references this article was fact-checked against.
- Destructuring assignment (MDN, official reference)developer.mozilla.org
- Default parameters (MDN, official reference)developer.mozilla.org





