Skip to content
C Codeloom
JavaScript

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.

·4 min read · By Codeloom
Beginner 9 min read

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.