TechEarl

JavaScript Destructuring: Objects, Arrays, Nesting, Aliases, and Defaults

A complete, practical guide to JavaScript destructuring: pull values out of objects and arrays, skip elements, gather a rest, rename with aliases, set defaults, destructure nested shapes and function arguments, and avoid the two gotchas that bite everyone.

Ishan Karunaratne⏱️ 7 min readUpdated
Share thisCopied
A complete guide to JavaScript destructuring: objects, arrays, skipping, rest, nesting, aliases, defaults, and function-argument destructuring with the empty-object gotcha.

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:

javascript
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 = 20

That 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."

javascript
const config = { host: "localhost", port: 5432, ssl: false };
const { host, port } = config;        // host = "localhost", port = 5432

Order 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:

javascript
const rgb = [255, 128, 0];
const [red, green, blue] = rgb;       // red = 255, green = 128, blue = 0

This is also the cleanest way to swap two variables with no temporary:

javascript
let a = 1, b = 2;
[a, b] = [b, a];                      // a = 2, b = 1

Skipping elements

Leave a hole in the array pattern to skip a position. The commas still count:

javascript
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.

javascript
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:

javascript
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.

javascript
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:

javascript
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:

javascript
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:

javascript
const { customer, customer: { address: { city } } } = order;
// now both `customer` (the object) and `city` (the string) exist

The 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:

javascript
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:

javascript
function te_connect({ host = "localhost", port = 5432, ssl = false } = {}) {
  return `${host}:${port}${ssl ? " (ssl)" : ""}`;
}

te_connect();                    // "localhost:5432", no throw

The 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:

javascript
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

Sources

Authoritative references this article was fact-checked against.

TagsJavaScriptdestructuringES6objectsarraysdefault parametersrest

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

Run JavaScript When a CSS Animation or Transition Ends

Fire a JavaScript callback when a CSS transition or animation finishes, using the transitionend and animationend events. The propertyName filtering, the bubbling trap, the cases where the event never fires, and the modern Web Animations API promise alternative.