Destructuring: Arrays & Objects
What Is Destructuring?
Destructuring is a special syntax introduced in ES6 (2015) that allows you to unpack values from arrays or properties from objects into distinct variables in a single, concise statement. Before destructuring, extracting multiple values from an array or object required multiple lines of code with repetitive access patterns. Destructuring simplifies this process dramatically, making your code shorter, cleaner, and more expressive. It is one of the most heavily used features in modern JavaScript and you will encounter it in virtually every modern codebase, framework, and library.
Destructuring does not modify the original array or object. It simply creates new variables and assigns values to them based on the structure of the data source. Think of it as a pattern-matching operation: you describe the shape of the data you expect, and JavaScript extracts the matching pieces for you.
Array Destructuring: The Basics
Array destructuring uses square bracket syntax on the left side of an assignment to extract values by their position (index) in the array. The first variable gets the first element, the second variable gets the second element, and so on.
Example: Basic Array Destructuring
// Without destructuring (the old way)
const colors = ['red', 'green', 'blue'];
const first = colors[0]; // 'red'
const second = colors[1]; // 'green'
const third = colors[2]; // 'blue'
// With destructuring (the modern way)
const [red, green, blue] = ['red', 'green', 'blue'];
console.log(red); // 'red'
console.log(green); // 'green'
console.log(blue); // 'blue'
// Destructuring an existing array
const scores = [95, 87, 72, 64, 91];
const [highest, secondHighest, thirdHighest] = scores;
console.log(highest); // 95
console.log(secondHighest); // 87
console.log(thirdHighest); // 72
Skipping Elements in Array Destructuring
You can skip elements in an array by using commas without variable names. Each comma represents one skipped position. This is useful when you only need specific elements from an array and want to ignore the rest.
Example: Skipping Elements
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];
// Skip the first two months, get March
const [, , march] = months;
console.log(march); // 'Mar'
// Get first and third elements only
const [first, , third] = months;
console.log(first); // 'Jan'
console.log(third); // 'Mar'
// Get first and last from a known-size array
const [january, , , , , june] = months;
console.log(january); // 'Jan'
console.log(june); // 'Jun'
The Rest Pattern with Arrays
The rest pattern (...) collects all remaining elements into a new array. It must be the last element in the destructuring pattern. This is extremely useful when you want to separate the first few elements from the rest.
Example: Rest Pattern in Array Destructuring
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Get the first two, collect the rest
const [first, second, ...remaining] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(remaining); // [3, 4, 5, 6, 7, 8, 9, 10]
// Skip one, get one, collect the rest
const [, secondItem, ...others] = numbers;
console.log(secondItem); // 2
console.log(others); // [3, 4, 5, 6, 7, 8, 9, 10]
// Practical example: separating head from tail
const queue = ['urgent-task', 'task-1', 'task-2', 'task-3'];
const [currentTask, ...pendingTasks] = queue;
console.log(currentTask); // 'urgent-task'
console.log(pendingTasks); // ['task-1', 'task-2', 'task-3']
const [...rest, last] = array; will throw a SyntaxError. If you need the last element, consider using array.at(-1) or array[array.length - 1] separately.Default Values in Array Destructuring
If the array does not have enough elements for the destructuring pattern, the extra variables will be undefined. You can assign default values that will be used when the corresponding array element is undefined or missing.
Example: Default Values
// Without defaults: missing values are undefined
const [a, b, c] = [1, 2];
console.log(a); // 1
console.log(b); // 2
console.log(c); // undefined
// With defaults: fallback values for missing elements
const [x = 10, y = 20, z = 30] = [5, 15];
console.log(x); // 5 (provided by array)
console.log(y); // 15 (provided by array)
console.log(z); // 30 (default used because element is missing)
// Practical example: configuration with defaults
const userSettings = ['dark'];
const [theme = 'light', fontSize = 16, language = 'en'] = userSettings;
console.log(theme); // 'dark' (from array)
console.log(fontSize); // 16 (default)
console.log(language); // 'en' (default)
Swapping Variables with Destructuring
One of the most elegant uses of array destructuring is swapping two variables without needing a temporary variable. Before ES6, swapping required a third variable to temporarily hold one of the values. Destructuring makes this a single, readable line.
Example: Variable Swapping
// Old way: using a temporary variable
let a = 'hello';
let b = 'world';
let temp = a;
a = b;
b = temp;
console.log(a, b); // 'world' 'hello'
// Modern way: destructuring swap
let x = 'hello';
let y = 'world';
[x, y] = [y, x];
console.log(x, y); // 'world' 'hello'
// Works with any data types
let first = 1;
let second = 2;
let third = 3;
[first, second, third] = [third, first, second];
console.log(first, second, third); // 3 1 2
Nested Array Destructuring
When arrays contain other arrays (nested or multidimensional arrays), you can use nested destructuring patterns to extract values at any depth. Simply use another set of square brackets in the position where the nested array exists.
Example: Nested Array Destructuring
// 2D array (matrix)
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// Destructure nested arrays
const [[a, b, c], [d, e, f], [g, h, i]] = matrix;
console.log(a, e, i); // 1 5 9 (the diagonal)
// Partially destructure nested arrays
const [firstRow, , thirdRow] = matrix;
console.log(firstRow); // [1, 2, 3]
console.log(thirdRow); // [7, 8, 9]
// Extract specific nested values
const [[topLeft], , [, , bottomRight]] = matrix;
console.log(topLeft); // 1
console.log(bottomRight); // 9
// Practical example: coordinate pairs
const coordinates = [[10, 20], [30, 40], [50, 60]];
const [[x1, y1], [x2, y2]] = coordinates;
console.log(x1, y1); // 10 20
console.log(x2, y2); // 30 40
Object Destructuring: The Basics
Object destructuring uses curly brace syntax to extract properties by their names. Unlike array destructuring where position matters, object destructuring matches by property name. The variable names must match the property names in the object (unless you rename them, which we will cover next).
Example: Basic Object Destructuring
// Without destructuring (the old way)
const person = {
name: 'Alice',
age: 28,
city: 'New York',
occupation: 'Engineer'
};
const name = person.name;
const age = person.age;
const city = person.city;
// With destructuring (the modern way)
const { name, age, city, occupation } = person;
console.log(name); // 'Alice'
console.log(age); // 28
console.log(city); // 'New York'
console.log(occupation); // 'Engineer'
// Order does not matter in object destructuring
const { occupation, name, city, age } = person; // Same result
Renaming Variables in Object Destructuring
Sometimes the property names in an object do not match the variable names you want to use, or they conflict with existing variables. You can rename properties during destructuring using the colon syntax: propertyName: newVariableName.
Example: Renaming During Destructuring
const apiResponse = {
user_name: 'john_doe',
user_email: 'john@example.com',
created_at: '2024-01-15',
is_active: true
};
// Rename snake_case to camelCase
const {
user_name: userName,
user_email: userEmail,
created_at: createdAt,
is_active: isActive
} = apiResponse;
console.log(userName); // 'john_doe'
console.log(userEmail); // 'john@example.com'
console.log(createdAt); // '2024-01-15'
console.log(isActive); // true
// Avoiding naming conflicts
const name = 'Global Name';
const user = { name: 'User Name', age: 25 };
const { name: userName2, age: userAge } = user;
console.log(name); // 'Global Name' (unchanged)
console.log(userName2); // 'User Name'
Default Values in Object Destructuring
Just like array destructuring, you can provide default values for object properties that might be undefined or missing. Defaults are especially useful when working with optional configuration objects or API responses that may have incomplete data.
Example: Default Values in Object Destructuring
const config = {
host: 'localhost',
port: 3000
};
// database and timeout use defaults since they are not in config
const {
host,
port,
database = 'mydb',
timeout = 5000,
retries = 3
} = config;
console.log(host); // 'localhost' (from object)
console.log(port); // 3000 (from object)
console.log(database); // 'mydb' (default)
console.log(timeout); // 5000 (default)
console.log(retries); // 3 (default)
// Combining rename and default
const settings = { bg: '#fff' };
const { bg: backgroundColor = '#000', fg: foregroundColor = '#333' } = settings;
console.log(backgroundColor); // '#fff' (from object)
console.log(foregroundColor); // '#333' (default, since fg is missing)
const { oldName: newName = defaultValue } = obj;. This reads as: extract the oldName property, call it newName, and if it does not exist, use defaultValue. This pattern is extremely common in configuration handling.The Rest Pattern with Objects
Just as with arrays, you can use the rest pattern with objects to collect all remaining properties into a new object. This is a powerful technique for separating known properties from the rest.
Example: Rest Pattern in Object Destructuring
const user = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
age: 28,
role: 'admin',
department: 'Engineering'
};
// Extract id and name, collect the rest
const { id, name, ...details } = user;
console.log(id); // 1
console.log(name); // 'Alice'
console.log(details); // { email: 'alice@example.com', age: 28, role: 'admin', department: 'Engineering' }
// Practical: removing a property from an object (immutably)
const { email, ...userWithoutEmail } = user;
console.log(userWithoutEmail);
// { id: 1, name: 'Alice', age: 28, role: 'admin', department: 'Engineering' }
// Practical: separating props in component-like pattern
const props = { className: 'card', onClick: () => {}, children: 'Hello', id: 'card-1', style: {} };
const { className, onClick, children, ...htmlAttributes } = props;
console.log(htmlAttributes); // { id: 'card-1', style: {} }
Nested Object Destructuring
Objects often contain other objects as properties, forming a nested structure. You can destructure nested objects by mirroring the nesting pattern in your destructuring syntax. This is extremely common when working with complex API responses or configuration objects.
Example: Nested Object Destructuring
const company = {
name: 'TechCorp',
address: {
street: '123 Main St',
city: 'San Francisco',
state: 'CA',
zip: '94102'
},
ceo: {
name: 'Jane Doe',
contact: {
email: 'jane@techcorp.com',
phone: '555-0123'
}
}
};
// Destructure nested properties
const {
name: companyName,
address: { city, state, zip },
ceo: { name: ceoName, contact: { email: ceoEmail } }
} = company;
console.log(companyName); // 'TechCorp'
console.log(city); // 'San Francisco'
console.log(state); // 'CA'
console.log(ceoName); // 'Jane Doe'
console.log(ceoEmail); // 'jane@techcorp.com'
undefined or null), you will get a TypeError. Always ensure the parent object exists, or provide a default empty object: const { address: { city } = {} } = obj;. This prevents crashes when dealing with incomplete data.Destructuring Function Parameters
One of the most powerful and practical uses of destructuring is in function parameters. Instead of passing a large object and accessing its properties inside the function with dot notation, you can destructure the parameter directly in the function signature. This makes the function interface self-documenting and reduces boilerplate code inside the function body.
Example: Destructuring Function Parameters
// Without destructuring: lots of obj.property access
function createUserOld(options) {
const name = options.name;
const email = options.email;
const role = options.role || 'user';
const active = options.active !== undefined ? options.active : true;
console.log(name + ' (' + email + ') - ' + role);
}
// With destructuring: clean and self-documenting
function createUser({ name, email, role = 'user', active = true }) {
console.log(name + ' (' + email + ') - ' + role);
console.log('Active: ' + active);
}
createUser({
name: 'Alice',
email: 'alice@example.com',
role: 'admin'
});
// Alice (alice@example.com) - admin
// Active: true
// Works great with arrow functions too
const formatAddress = ({ street, city, state, zip }) => {
return street + ', ' + city + ', ' + state + ' ' + zip;
};
console.log(formatAddress({
street: '456 Oak Ave',
city: 'Portland',
state: 'OR',
zip: '97201'
}));
// 456 Oak Ave, Portland, OR 97201
Example: Optional Parameters with Default Empty Object
// Making the entire parameter optional
function initApp({ debug = false, verbose = false, port = 3000 } = {}) {
console.log('Debug: ' + debug);
console.log('Verbose: ' + verbose);
console.log('Port: ' + port);
}
// Call with options
initApp({ debug: true, port: 8080 });
// Debug: true
// Verbose: false
// Port: 8080
// Call with no arguments at all (uses all defaults)
initApp();
// Debug: false
// Verbose: false
// Port: 3000
= {} default at the end of the parameter ({ debug, verbose } = {}) is crucial. Without it, calling the function with no arguments would throw a TypeError because JavaScript would try to destructure undefined. The empty object default ensures the function works even when called without arguments.Destructuring in Loops
Destructuring works seamlessly with for...of loops, making it easy to work with arrays of objects or entries from a Map. This pattern eliminates the need for temporary variables and makes loop bodies much cleaner.
Example: Destructuring in for...of Loops
const users = [
{ name: 'Alice', age: 28, role: 'admin' },
{ name: 'Bob', age: 34, role: 'editor' },
{ name: 'Charlie', age: 22, role: 'user' }
];
// Destructure each object in the loop
for (const { name, age, role } of users) {
console.log(name + ' is ' + age + ' years old and is an ' + role);
}
// Alice is 28 years old and is an admin
// Bob is 34 years old and is an editor
// Charlie is 22 years old and is an user
// Destructuring with Object.entries()
const scores = { math: 95, science: 88, english: 92, history: 78 };
for (const [subject, score] of Object.entries(scores)) {
console.log(subject + ': ' + score);
}
// math: 95
// science: 88
// english: 92
// history: 78
// Destructuring with Map entries
const userMap = new Map([
['u001', { name: 'Alice', active: true }],
['u002', { name: 'Bob', active: false }]
]);
for (const [id, { name, active }] of userMap) {
console.log(id + ': ' + name + ' (active: ' + active + ')');
}
// u001: Alice (active: true)
// u002: Bob (active: false)
Mixed Destructuring: Arrays of Objects
Real-world data often combines arrays and objects in various configurations. You can freely mix array and object destructuring to extract exactly the values you need from complex nested structures. This is one of the patterns you will use most often in practice.
Example: Mixed Destructuring Patterns
// Array of objects: get specific items and their properties
const products = [
{ id: 1, name: 'Laptop', price: 999, category: 'Electronics' },
{ id: 2, name: 'Book', price: 29, category: 'Education' },
{ id: 3, name: 'Headphones', price: 149, category: 'Electronics' }
];
// Destructure the first product's name and price
const [{ name: firstName, price: firstPrice }] = products;
console.log(firstName); // 'Laptop'
console.log(firstPrice); // 999
// Skip first, destructure second
const [, { name: secondName, category }] = products;
console.log(secondName); // 'Book'
console.log(category); // 'Education'
// Get first item, collect rest
const [featured, ...otherProducts] = products;
console.log(featured.name); // 'Laptop'
console.log(otherProducts.length); // 2
// Object containing arrays
const dashboard = {
title: 'Analytics',
metrics: [120, 340, 560, 230],
users: ['Alice', 'Bob', 'Charlie']
};
const {
title,
metrics: [first, second, ...restMetrics],
users: [primaryUser, ...otherUsers]
} = dashboard;
console.log(title); // 'Analytics'
console.log(first); // 120
console.log(second); // 340
console.log(restMetrics); // [560, 230]
console.log(primaryUser); // 'Alice'
console.log(otherUsers); // ['Bob', 'Charlie']
Real-World: API Response Destructuring
One of the most common real-world applications of destructuring is processing API responses. APIs often return deeply nested JSON data, and destructuring lets you extract exactly the data you need in a clean, readable way. Let us look at several practical examples that mirror what you will encounter in production applications.
Example: Destructuring a REST API User Response
// Simulated API response
const apiResponse = {
status: 200,
data: {
user: {
id: 42,
profile: {
firstName: 'Sarah',
lastName: 'Ahmed',
avatar: 'https://example.com/avatar.jpg'
},
settings: {
theme: 'dark',
notifications: {
email: true,
push: false,
sms: true
}
},
posts: [
{ id: 101, title: 'Hello World', likes: 42 },
{ id: 102, title: 'JavaScript Tips', likes: 128 },
{ id: 103, title: 'CSS Tricks', likes: 95 }
]
}
},
meta: {
requestId: 'abc-123',
timestamp: '2024-01-15T10:30:00Z'
}
};
// Deep destructuring to get exactly what we need
const {
status,
data: {
user: {
id: userId,
profile: { firstName, lastName, avatar },
settings: {
theme,
notifications: { email: emailNotifications, push: pushNotifications }
},
posts: [latestPost, ...olderPosts]
}
},
meta: { requestId }
} = apiResponse;
console.log(status); // 200
console.log(userId); // 42
console.log(firstName); // 'Sarah'
console.log(lastName); // 'Ahmed'
console.log(theme); // 'dark'
console.log(emailNotifications); // true
console.log(pushNotifications); // false
console.log(latestPost.title); // 'Hello World'
console.log(olderPosts.length); // 2
console.log(requestId); // 'abc-123'
Example: Destructuring Fetch Response with Error Handling
// Common pattern for handling API responses
async function fetchUserData(userId) {
try {
const response = await fetch('/api/users/' + userId);
const json = await response.json();
// Destructure with defaults for safety
const {
data: {
name = 'Unknown',
email = 'N/A',
role = 'user',
permissions = []
} = {},
error = null,
message = ''
} = json;
if (error) {
console.log('Error: ' + message);
return null;
}
return { name, email, role, permissions };
} catch (err) {
console.log('Request failed: ' + err.message);
return null;
}
}
// Using the destructured result
async function displayUser() {
const userData = await fetchUserData(42);
if (userData) {
const { name, email, role } = userData;
console.log(name + ' (' + email + ') - ' + role);
}
}
Example: Destructuring a Paginated API Response
const paginatedResponse = {
results: [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' }
],
pagination: {
currentPage: 2,
totalPages: 10,
perPage: 3,
totalItems: 30
},
links: {
self: '/api/posts?page=2',
next: '/api/posts?page=3',
prev: '/api/posts?page=1',
first: '/api/posts?page=1',
last: '/api/posts?page=10'
}
};
const {
results: posts,
pagination: { currentPage, totalPages, totalItems },
links: { next: nextPageUrl, prev: prevPageUrl }
} = paginatedResponse;
console.log('Page ' + currentPage + ' of ' + totalPages);
console.log('Showing ' + posts.length + ' of ' + totalItems + ' items');
console.log('Next: ' + nextPageUrl);
console.log('Prev: ' + prevPageUrl);
// Iterate through results with destructuring
posts.forEach(({ id, title }) => {
console.log('#' + id + ': ' + title);
});
Destructuring with Computed Property Names
You can use computed (dynamic) property names in object destructuring by wrapping the property name in square brackets. This is useful when the property name is stored in a variable or determined at runtime.
Example: Computed Property Names in Destructuring
const data = {
name: 'Alice',
age: 28,
email: 'alice@example.com'
};
// Dynamic property name
const key = 'email';
const { [key]: extractedValue } = data;
console.log(extractedValue); // 'alice@example.com'
// Practical: extracting a specific field dynamically
function getField(obj, fieldName) {
const { [fieldName]: value } = obj;
return value;
}
console.log(getField(data, 'name')); // 'Alice'
console.log(getField(data, 'age')); // 28
console.log(getField(data, 'email')); // 'alice@example.com'
Destructuring Return Values
Functions that return arrays or objects are natural candidates for destructuring. Many built-in JavaScript methods and modern library functions return structured data that can be destructured immediately at the call site.
Example: Destructuring Function Return Values
// Function returning an array (like React's useState)
function useState(initialValue) {
let value = initialValue;
const setValue = (newValue) => { value = newValue; };
return [value, setValue];
}
const [count, setCount] = useState(0);
console.log(count); // 0
// Function returning an object
function getMinMax(numbers) {
return {
min: Math.min(...numbers),
max: Math.max(...numbers),
range: Math.max(...numbers) - Math.min(...numbers),
count: numbers.length
};
}
const { min, max, range } = getMinMax([5, 2, 8, 1, 9, 3]);
console.log(min); // 1
console.log(max); // 9
console.log(range); // 8
// Destructuring regex match results
const dateString = '2024-03-15';
const [fullMatch, year, month, day] = dateString.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(year); // '2024'
console.log(month); // '03'
console.log(day); // '15'
Common Destructuring Patterns Summary
Here is a quick reference of the most important destructuring patterns you will use daily:
- Array basics --
const [a, b, c] = array;extracts by position. - Skip elements --
const [a, , c] = array;uses commas to skip. - Rest (array) --
const [first, ...rest] = array;collects remaining elements. - Default values --
const [a = 1, b = 2] = array;fallback for undefined. - Swap variables --
[a, b] = [b, a];no temporary variable needed. - Object basics --
const { name, age } = obj;extracts by property name. - Rename --
const { name: userName } = obj;extract and rename. - Rest (object) --
const { id, ...rest } = obj;collects remaining properties. - Nested --
const { a: { b } } = obj;extracts from deeply nested structures. - Parameters --
function fn({ x, y } = {}) {}destructure in function signatures. - Loops --
for (const { name } of array) {}destructure in iterations.
Practice Exercise
Create a function called processApiResponse that accepts the following simulated API response object and uses destructuring to extract and process the data. The response has a status (number), a data object containing a users array (each user has id, name, email, address with city and country, and orders which is an array of objects with orderId, total, and items), and a pagination object with page, totalPages, and hasMore. Inside the function: use destructuring to extract status and the users array with a default of empty array; use destructuring in a for...of loop to iterate through users and extract their name, address: { city }, and first order using array destructuring [firstOrder, ...otherOrders]; for each user, log their name, city, and first order total; use the rest pattern to separate pagination from the rest of the response; return an object with the processed data. Test your function with sample data that includes at least 3 users, each with at least 2 orders, and verify that all destructuring patterns work correctly with both complete and incomplete data by testing with a user that has no orders (empty array) and missing address fields.