📦 Destructuring Objects and Arrays
Destructuring lets you pull values out of objects and arrays in a single, readable line instead of a stack of repetitive assignments. It is one of those ES6 features that, once it clicks, you use dozens of times a day — in function parameters, API handling, React props, and everywhere data arrives in a bundle.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Destructure objects — with renaming, default values, and nesting
- Destructure arrays by position, skip elements, and swap variables without a temp
- Collect leftovers with the rest pattern in both objects and arrays
- Use destructuring in function parameters for clean, self-documenting signatures
- Avoid the common null/undefined pitfalls that throw errors
Estimated Time: 30–40 minutes • Difficulty: Intermediate
Hands-on: Extract fields from a realistic API response, then refactor a settings function to use parameter destructuring with defaults.
In This Lesson
What Is Destructuring?
Destructuring, introduced in ES6 (2015), is a syntax for "unpacking" values from objects and arrays into distinct variables. Instead of reaching into a structure one property at a time, you describe the shape you want on the left-hand side, and JavaScript fills in the variables for you.
💡 An analogy: Destructuring is like unpacking a grocery bag onto the counter. Rather than asking "what's in slot zero? what's in slot one?", you lay everything out at once and label it — milk here, bread there — in one motion.
The payoff is real: fewer lines, clearer intent, built-in defaults for missing data, and painless handling of nested structures.
Object Destructuring
The basic form pulls properties into variables that share the property's name:
const person = { firstName: 'John', lastName: 'Doe', age: 30, job: 'Developer' };
// Instead of four repetitive lines...
const { firstName, lastName, age, job } = person;
console.log(firstName); // 'John'
console.log(job); // 'Developer'
Renaming
Use property: newName when you want a different variable name — handy when the source name would clash or is unclear:
const person = { firstName: 'John', lastName: 'Doe' };
const { firstName: fName, lastName: lName } = person;
console.log(fName); // 'John'
console.log(lName); // 'Doe'
// firstName is not defined here — only fName and lName are
Default values
A default kicks in only when the property is undefined (missing). You can combine defaults with renaming:
const person = { firstName: 'John', lastName: 'Doe' }; // no job
const { firstName, job = 'Unknown', salary = 0 } = person;
console.log(job); // 'Unknown' (default applied)
console.log(salary); // 0 (default applied)
// Rename + default together
const { job: occupation = 'Unemployed' } = person;
console.log(occupation); // 'Unemployed'
Nested objects
const user = {
id: 42,
name: 'John Doe',
address: {
city: 'Boston',
coordinates: { latitude: 42.3601, longitude: -71.0589 }
}
};
const { name, address: { city, coordinates: { latitude, longitude } } } = user;
console.log(city); // 'Boston'
console.log(latitude); // 42.3601
// Note: `address` and `coordinates` are NOT created as variables —
// they are just paths you traverse. Ask for them explicitly if you need them.
The rest pattern
const person = { firstName: 'John', lastName: 'Doe', age: 30, job: 'Developer' };
const { firstName, ...rest } = person;
console.log(firstName); // 'John'
console.log(rest); // { lastName: 'Doe', age: 30, job: 'Developer' }
Array Destructuring
Arrays unpack by position, so the variable names are yours to choose:
const colors = ['red', 'green', 'blue', 'yellow'];
const [first, second, third] = colors;
console.log(first); // 'red'
console.log(second); // 'green'
console.log(third); // 'blue'
Skipping elements and defaults
const scores = ['red', 'green', 'blue', 'yellow', 'purple'];
// Leave a gap with an empty comma to skip a position
const [gold, , bronze] = scores;
console.log(gold, bronze); // 'red' 'blue'
// Defaults for missing positions
const [primary = 'crimson', secondary = 'emerald', tertiary = 'azure'] = ['red', 'green'];
console.log(tertiary); // 'azure' (default — the array had no third item)
Swapping without a temp variable
let x = 5;
let y = 10;
[x, y] = [y, x]; // no temporary variable needed
console.log(x, y); // 10 5
Rest and nesting
const numbers = [1, 2, 3, 4, 5];
const [head, ...tail] = numbers;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// Nested arrays mirror their shape
const nested = [1, [2, 3], [4, [5, 6]]];
const [a, [b, c], [d, [e, f]]] = nested;
console.log(a, b, c, d, e, f); // 1 2 3 4 5 6
📖 Where array destructuring shines
React's useState hook returns a two-element array — const [count, setCount] = useState(0) — which is array destructuring in action. So is grabbing regex match groups: const [, year, month] = dateString.match(/(\d{4})-(\d{2})/).
Combining Objects & Arrays
Real data mixes both shapes. You can nest object and array patterns as deeply as the data goes:
const person = {
name: 'John Doe',
location: { city: 'New York', country: 'USA' },
education: ['High School', 'Bachelor', 'Master'],
languages: [
{ name: 'English', level: 'native' },
{ name: 'Spanish', level: 'intermediate' }
]
};
const {
name,
location: { city, country },
education: [, , highestEducation], // skip first two, keep the third
languages: [{ level: englishLevel }, { name: secondLanguage }]
} = person;
console.log(name); // 'John Doe'
console.log(city, country); // 'New York' 'USA'
console.log(highestEducation); // 'Master'
console.log(englishLevel); // 'native'
console.log(secondLanguage); // 'Spanish'
Destructuring in Parameters
Destructuring in a function's parameter list is one of its most valuable uses. The signature documents which fields the function needs, and callers can pass properties in any order.
// Traditional — the body has to reach into `user` repeatedly
function displayUser(user) {
console.log(`Name: ${user.name}, City: ${user.city || 'Unknown'}`);
}
// Destructured parameter with a default — cleaner and self-describing
function displayUser({ name, age, city = 'Unknown' }) {
console.log(`Name: ${name}, Age: ${age}, City: ${city}`);
}
displayUser({ name: 'John', age: 30 });
// Name: John, Age: 30, City: Unknown
Array parameters destructure by position, which reads beautifully for coordinate-like data:
function distanceFromOrigin([x, y]) {
return Math.sqrt(x * x + y * y);
}
console.log(distanceFromOrigin([3, 4])); // 5
Options objects with a full default
A common professional pattern: accept a single options object, destructure it with defaults, and default the whole object to {} so the function can be called with no arguments at all.
function setupApp({
environment = 'development',
database = { host: 'localhost', port: 5432 },
features = { darkMode: true, notifications: true }
} = {}) {
console.log(`Env: ${environment}`);
console.log(`DB: ${database.host}:${database.port}`);
console.log(`Features: ${Object.keys(features).filter(f => features[f]).join(', ')}`);
}
setupApp(); // all defaults
setupApp({ environment: 'production' }); // override just one
⚠️ Don't forget the = {}
Without the trailing = {}, calling setupApp() with no arguments throws, because you can't destructure undefined. The = {} gives the parameter an empty object to unpack when nothing is passed.
Gotchas & Null Safety
Destructuring null or undefined throws
const user = null;
// const { name } = user;
// → TypeError: Cannot destructure property 'name' of 'null' as it is null.
// Guard with the nullish coalescing operator (??) — supply a fallback object
const { name } = user ?? {};
console.log(name); // undefined, no crash
Computed (dynamic) property names
const key = 'title';
const settings = { id: 42, title: 'My Settings', theme: 'dark' };
const { [key]: value } = settings; // use the variable's value as the key
console.log(value); // 'My Settings'
Nested defaults are subtler than they look
const settings = { theme: { font: 'Arial' } }; // theme exists, but has no color
// Because `theme` exists, its `= {}` default never applies,
// so `color` falls through to its own default of 'blue':
const { theme: { color = 'blue' } = {} } = settings;
console.log(color); // 'blue'
// If `theme` were missing entirely, the `= {}` would kick in first,
// and color would still resolve to 'blue'. Test both cases when it matters.
✅ Practical rule
For anything that might be missing at runtime — API payloads, user input, optional config — default the container (?? {} or = {}) and default the fields. Two small guards prevent the most common destructuring crash.
Hands-on Exercise
🏋️ Unpack an API Response
Objective: Extract nested values in one statement, then refactor a function to use parameter destructuring.
Part A — Extract from a response
From the response below, use a single destructuring statement to create username, fullName (combine first and last), and firstPostTitle.
const apiResponse = {
status: 'success',
data: {
user: {
username: 'jsmith',
profile: { firstName: 'John', lastName: 'Smith' }
},
posts: [
{ id: 101, title: 'First Post' },
{ id: 102, title: 'Second Post' }
]
}
};
// Goal: username → 'jsmith', firstName + lastName → 'John Smith', firstPostTitle → 'First Post'
Part B — Refactor with defaults
Rewrite this function so it destructures its options object in the parameter list, defaults currency to 'USD' and tax to 0, and can be called with no arguments.
function priceLabel(options) {
const amount = options.amount;
const currency = options.currency || 'USD';
const tax = options.tax || 0;
return `${(amount + tax).toFixed(2)} ${currency}`;
}
💡 Hint
For Part A, mirror the nesting: data: { user: { … }, posts: [ … ] }. Rename firstName/lastName if you like, then template-string them together. For Part B, put { amount, currency = 'USD', tax = 0 } = {} directly in the parameter list.
✅ Solution
// Part A
const {
data: {
user: { username, profile: { firstName, lastName } },
posts: [{ title: firstPostTitle }]
}
} = apiResponse;
const fullName = `${firstName} ${lastName}`;
console.log(username, fullName, firstPostTitle);
// 'jsmith' 'John Smith' 'First Post'
// Part B
function priceLabel({ amount, currency = 'USD', tax = 0 } = {}) {
return `${(amount + tax).toFixed(2)} ${currency}`;
}
console.log(priceLabel({ amount: 20, tax: 1.5 })); // '21.50 USD'
Quiz
🎯 Check Your Understanding
Question 1: After const { a: x, b = 5 } = { a: 1 };, what are the values of x and b?
Question 2: Why does function f({ a } = {}) include the = {}?
Question 3: How does array destructuring decide which value goes into which variable?
Summary & What's Next
🎉 Key Takeaways
- Objects destructure by name (with renaming and defaults); arrays destructure by position (skip with empty commas).
- A default applies only when a value is
undefined(missing). - Nesting mirrors the data's shape; intermediate containers are traversed, not created as variables.
- Parameter destructuring makes signatures self-documenting — remember
= {}so the function survives being called with nothing. - You cannot destructure
null/undefined; guard with?? {}.
📚 Further Reading
- MDN — Destructuring assignment
- javascript.info — Destructuring assignment
- MDN — Nullish coalescing (
??)
🚀 What's Next?
You met the rest pattern (...rest) a few times here. Its mirror image, the spread operator, uses the same three dots to do the opposite — expand a structure out. Next we'll cover both spread and rest side by side.
🎉 Great progress!
Destructuring will quietly clean up your code everywhere data arrives in bundles. Next up: spread and rest.