JavaScript Destructuring, Spread, and Rest
A complete guide to destructuring objects and arrays, the spread operator, and rest parameters in modern JavaScript — with practical patterns for clean code.
What you'll learn
- ✓Object and array destructuring patterns
- ✓Default values and renaming
- ✓Spread for arrays and objects
- ✓Rest parameters in functions
- ✓Real-world refactoring with these features
Prerequisites
- •Familiarity with JavaScript basics
Destructuring, spread, and rest are three of the most beloved features of modern JavaScript. They share the ... syntax and the goal of making data manipulation concise. This tutorial walks through each one with examples you can drop straight into your code.
Object Destructuring
Object destructuring lets you pull properties out of an object into local variables in one line. The variable name must match the property name unless you rename it.
const user = { name: 'Ada', age: 36, role: 'admin' };
const { name, age } = user;
console.log(name, age); // Ada 36
You can rename on the fly using a colon, which is useful when a property collides with an existing variable.
const { name: userName } = user;
Defaults kick in when the property is undefined, not when it is null or another falsy value. This subtlety matters.
const { theme = 'light' } = { theme: undefined };
console.log(theme); // 'light'
Array Destructuring
Array destructuring works by position rather than by name. Skip elements by leaving the slot empty.
const colors = ['red', 'green', 'blue'];
const [first, , third] = colors;
console.log(first, third); // red blue
It pairs beautifully with functions that return tuples, like React hooks. The useState hook returns a two-element array that you destructure into a value and a setter.
Nested Destructuring
You can dive into nested structures, but keep readability in mind. Once you have more than two levels of nesting, consider splitting into multiple statements.
const response = { data: { user: { id: 1, name: 'Ada' } } };
const { data: { user: { name } } } = response;
If the path may be missing, prefer optional chaining followed by destructuring to avoid TypeError crashes.
The Spread Operator
Spread expands an iterable into individual elements. With arrays, it copies and combines without mutating the original.
const a = [1, 2];
const b = [3, 4];
const merged = [...a, ...b]; // [1, 2, 3, 4]
With objects, spread copies enumerable own properties. Later keys override earlier ones, which is perfect for setting defaults.
const defaults = { theme: 'light', size: 'md' };
const settings = { ...defaults, size: 'lg' };
// { theme: 'light', size: 'lg' }
Remember that spread is shallow. Nested objects are still shared by reference. For deep clones reach for structuredClone.
Rest Parameters
Rest collects remaining values into an array. In function signatures, it captures all extra arguments after the named ones.
function logAll(first, ...rest) {
console.log('first:', first);
console.log('rest:', rest);
}
logAll(1, 2, 3, 4); // first: 1, rest: [2, 3, 4]
You can also use rest in destructuring to grab everything else from an object or array.
const { id, ...withoutId } = { id: 1, name: 'Ada', age: 36 };
console.log(withoutId); // { name: 'Ada', age: 36 }
This pattern is great for excluding sensitive fields before sending data to a client.
Function Parameter Patterns
Combining destructuring with function parameters gives you named arguments in JavaScript. It also documents the function’s contract at the signature.
function createUser({ name, role = 'user', active = true } = {}) {
return { name, role, active, createdAt: Date.now() };
}
createUser({ name: 'Ada', role: 'admin' });
The trailing = {} ensures the function does not throw if called with no arguments. Without it, destructuring undefined would crash.
Practical Refactor
Imagine merging form values with previous state in a React reducer. With spread, the update is a one-liner that keeps the old state intact.
function reducer(state, action) {
switch (action.type) {
case 'update':
return { ...state, ...action.payload };
default:
return state;
}
}
Without spread, you would manually copy each key, which is verbose and error-prone.
Wrapping Up
These three features pay for themselves many times over in real codebases. They reduce boilerplate, communicate intent, and make immutable updates natural. Practice them until they feel automatic, and your JavaScript will look modern and clean.
Related articles
- JavaScript Destructuring, Spread, and Rest in JavaScript
A complete tour of the ES6 patterns that show up everywhere — object and array destructuring with defaults and renaming, the rest pattern, and spread for copies and merges.
- JavaScript The JavaScript Event Loop, Explained Clearly
A deep but practical look at the JavaScript event loop: call stack, microtasks, macrotasks, and how async code actually runs in the browser and Node.
- JavaScript JavaScript Generators and Iterators
A practical guide to JavaScript iterators and generator functions: the protocols, lazy sequences, async generators, and where they shine in real code.
- JavaScript JavaScript Modules: ESM vs CommonJS
A practical comparison of ES Modules and CommonJS: how they load, how they differ, interop strategies, and how to choose for a new project.