Destructuring is a syntax feature that lets you unpack values from arrays and properties from objects into distinct variables. It was introduced in ES6 and has become one of the most frequently used JavaScript features — you’ll see it in nearly every modern codebase for extracting API response fields, function parameters, and import/export patterns.
The core value is readability: instead of accessing properties repeatedly through dot notation, destructuring makes the intent explicit at the point of assignment.
Array Destructuring
Array destructuring extracts values by position. The pattern on the left mirrors the structure of the array on the right:
const coordinates = [40.7128, -74.0060];
const [lat, lng] = coordinates;
console.log(lat); // 40.7128
console.log(lng); // -74.0060
You can skip positions with commas, collect remaining elements with rest (...), and provide defaults for missing positions:
const [first, , third] = [1, 2, 3]; // skip index 1
console.log(first, third); // 1, 3
const [head, ...tail] = [1, 2, 3, 4, 5]; // head=1, tail=[2,3,4,5]
const [x = 0, y = 0, z = 0] = [10, 20]; // z defaults to 0 (position not in array)
console.log(x, y, z); // 10, 20, 0
Variable Swapping
Swapping two variables without a temporary variable is one of the cleaner uses of array destructuring:
let a = 'left';
let b = 'right';
[a, b] = [b, a];
console.log(a, b); // 'right', 'left'
Before this, you needed a third variable to hold the intermediate value.
Returning Multiple Values from Functions
Functions can only return one value, but that value can be an array, and destructuring lets you capture the parts clearly:
function minMax(numbers) {
return [Math.min(...numbers), Math.max(...numbers)];
}
const [min, max] = minMax([3, 1, 4, 1, 5, 9, 2, 6]);
console.log(min, max); // 1, 9
Object Destructuring
Object destructuring extracts properties by name — position doesn’t matter. The left side lists the property names you want to extract:
const user = { id: 1, name: 'Alice', email: '[email protected]', role: 'admin' };
const { name, email } = user;
console.log(name); // 'Alice'
console.log(email); // '[email protected]'
// Properties not listed (id, role) are simply not extracted
Renaming
When the property name conflicts with an existing variable or you want a different local name, use : to rename:
const { name: userName, role: userRole } = user;
console.log(userName); // 'Alice'
console.log(userRole); // 'admin'
Default Values
Defaults apply when the property is undefined (missing from the object or explicitly set to undefined):
const config = { host: 'localhost' };
const { host, port = 3000, ssl = false } = config;
console.log(host); // 'localhost'
console.log(port); // 3000 (default — not in config)
console.log(ssl); // false (default — not in config)
Defaults and renaming can be combined: { port: serverPort = 3000 } extracts port, renames it to serverPort, and defaults to 3000.
Rest Properties
Collect remaining properties into a new object with ...:
const { id, password, ...safeUser } = { id: 1, name: 'Alice', email: '[email protected]', password: 'secret' };
// safeUser = { name: 'Alice', email: '[email protected]' } — password excluded
// Useful when you want to strip sensitive fields before sending to a client
Nested Destructuring
Access deeply nested properties by mirroring the structure:
const response = {
status: 200,
data: {
user: {
id: 42,
name: 'Alice',
address: { city: 'New York', zip: '10001' }
}
}
};
const { data: { user: { name, address: { city } } } } = response;
console.log(name); // 'Alice'
console.log(city); // 'New York'
Nested destructuring can get hard to read. If the nesting is more than two levels deep, consider extracting intermediate variables:
// More readable — intermediate variable breaks the depth
const { data: { user } } = response;
const { name, address: { city } } = user;
Destructuring in Function Parameters
Destructuring parameters is common in React (props), Redux (reducers), and any function that accepts option objects. It documents exactly which properties the function uses:
// Without destructuring — you have to read the function body to know what's used
function createUser(options) {
const name = options.name;
const role = options.role || 'user';
const active = options.active !== undefined ? options.active : true;
return { name, role, active };
}
// With destructuring — the signature is self-documenting
function createUser({ name, role = 'user', active = true } = {}) {
return { name, role, active };
}
createUser({ name: 'Alice' }); // { name: 'Alice', role: 'user', active: true }
createUser({ name: 'Bob', role: 'admin', active: false }); // { name: 'Bob', role: 'admin', active: false }
createUser(); // { name: undefined, role: 'user', active: true }
The = {} default at the end means calling createUser() with no arguments works instead of throwing “Cannot destructure property ’name’ of undefined.”
For React function components, destructuring props directly in the parameter is the standard pattern:
function UserCard({ name, email, role = 'user', onEdit }) {
return (
<div>
<h3>{name}</h3>
<p>{email}</p>
<span>{role}</span>
<button onClick={onEdit}>Edit</button>
</div>
);
}
Real-World Patterns
Extracting API Response Fields
When working with API responses, you typically want specific nested fields — destructuring makes the extraction clear and concise:
async function loadProfile(userId) {
const res = await fetch(`/api/users/${userId}`);
const { data: { name, email, avatar }, meta: { lastLogin } } = await res.json();
return { name, email, avatar, lastLogin };
}
Iterating Objects
Object.entries() returns [key, value] pairs, which combine naturally with array destructuring in loops:
const scores = { alice: 95, bob: 87, carol: 92 };
for (const [name, score] of Object.entries(scores)) {
console.log(`${name}: ${score}`);
}
// Also useful with .map()
const rankings = Object.entries(scores)
.sort(([, a], [, b]) => b - a) // sort by score descending
.map(([name, score], index) => `${index + 1}. ${name} (${score})`);
Filtering and Transforming Arrays of Objects
Destructuring inside .map() and .filter() makes transformations readable:
const users = [
{ id: 1, name: 'Alice', active: true, role: 'admin' },
{ id: 2, name: 'Bob', active: false, role: 'user' },
{ id: 3, name: 'Carol', active: true, role: 'user' },
];
// Extract only the fields needed for a dropdown
const options = users
.filter(({ active }) => active)
.map(({ id, name }) => ({ value: id, label: name }));
// [{ value: 1, label: 'Alice' }, { value: 3, label: 'Carol' }]
Import Destructuring
ES module imports use the same destructuring syntax — you’re extracting named exports from a module’s exported object:
import { useState, useEffect, useCallback } from 'react';
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
Common Mistakes
Destructuring null or undefined throws a TypeError. Guard when the source might be missing:
// ❌ Throws if response.data is null
const { user } = response.data;
// ✅ Provide a fallback
const { user } = response.data ?? {};
// or check first
if (response.data) {
const { user } = response.data;
}
Object destructuring at statement start requires parens when used without const/let/var, because { at the start of a statement is parsed as a block:
let name, age;
// ❌ SyntaxError — { is parsed as a block statement
{ name, age } = { name: 'Alice', age: 30 };
// ✅ Wrap in parens
({ name, age } = { name: 'Alice', age: 30 });
Summary
Destructuring reduces boilerplate and makes data shapes explicit. The patterns you’ll use most often:
const { a, b } = obj— extract named propertiesconst { a: localName, b = default } = obj— rename and defaultconst [first, ...rest] = arr— head and tail of an arrayconst { sensitive, ...safe } = obj— strip properties with restfunction f({ option = default } = {})— self-documenting option objects
The syntax always mirrors the shape of the data you’re extracting from.
Comments