Destructuring and Spread

Learn how to extract values from arrays and objects using destructuring syntax, gather remaining values with rest syntax, expand values with the spread operator, and understand the shallow-copy pitfalls both share.

Destructuring and spread are two closely related pieces of syntax, both built around the same [ ] and { } shapes you already use for arrays and objects, but used to unpack values out of a structure (destructuring) or expand values into a new structure (spread). Together they make extracting and copying data dramatically shorter and more readable than manual index/property access.

Array destructuring

Array destructuring lets you unpack values from an array into individual variables, by position, using a matching square-bracket pattern on the left side of an assignment: const [first, second] = [10, 20] assigns first = 10 and second = 20 in a single line.

You can skip elements you don't need by leaving a gap (const [, second] = arr), and you can provide default values for when the array doesn't have enough elements (const [a = 1, b = 2] = [5]).

Object destructuring

Object destructuring unpacks values by property name rather than position, using a matching curly-brace pattern: const { name, age } = user pulls out the name and age properties into variables of the same name. You can rename the extracted variable with a colon (const { name: userName } = user) and provide default values the same way arrays do (const { role = "guest" } = user).

Object destructuring is extremely common in function parameters, letting a function accept a single options object but read individual named fields directly, for example function createUser({ name, email }) { ... }.

Rest syntax

The rest pattern (...) used inside a destructuring pattern collects whatever is left over into a new array or object. const [first, ...rest] = [1, 2, 3, 4] gives first = 1 and rest = [2, 3, 4]. The same idea works for objects: const { id, ...otherFields } = user gathers every property except id into a new otherFields object.

Rest syntax must always come last in a destructuring pattern — you can't have anything after ...rest, since it's defined as "everything that's left."

Spread syntax

The spread operator (...) used inside an array or object literal (not a destructuring pattern) does the opposite of rest: it expands an existing array or object's elements/properties out into a new one. [...arr1, ...arr2] combines two arrays into a new array; { ...obj1, ...obj2 } merges two objects into a new object, with later properties overwriting earlier ones on conflicts, just like Object.assign.

Spread is also commonly used to pass an array's elements as individual function arguments (myFunction(...args)), and to quickly create a shallow copy of an array or object: const copy = [...original] or const copy = { ...original }.

• Rest (...) appears in a destructuring pattern (left side) and collects remaining items • Spread (...) appears in an array/object literal or function call (right side / usage site) and expands items out

Shallow copies and common pitfalls

Both the spread operator and Object.assign create only a shallow copy: top-level properties are copied as new, independent values, but if a property's value is itself an object or array, that nested structure is still shared by reference between the original and the copy — mutating a nested object inside the copy will also change it inside the original.

A second common pitfall is forgetting that destructuring an object requires the object (or something evaluating to undefined-safe) to actually exist — destructuring properties off of undefined or null throws a TypeError, so default parameters or optional chaining are often needed when the source might be missing.

Destructuring and spread side by side

rest (...rest) must be the last item in a destructuring pattern. Spread inside {} or [] literals expands an existing structure's contents into the new one being built. Later spread sources override earlier ones when object keys collide, same as Object.assign.

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];

// Object destructuring, with renaming and default
const { name: userName, role = "guest" } = { name: "Ada" };

// Spread — combining arrays
const combined = [...[1, 2], ...[3, 4]]; // [1, 2, 3, 4]

// Spread — combining/overriding objects
const merged = { ...{ a: 1, b: 2 }, ...{ b: 99 } }; // { a: 1, b: 99 }

Code Examples

Array destructuring pulls latitude and longitude out by position; object destructuring pulls name and age out by matching property name. Both replace what would otherwise take two separate lines of manual index/property access. Expected result: 12.5 45.2 Lin 30 const [latitude, longitude] = coordinates; assigns two variables from array positions 0 and 1 in one statement.

describeUser destructures its single object parameter directly in the function signature, including a default value for role when it's not provided. Separately, rest syntax pulls apples out on its own while otherFruit collects everything else from inventory into a new object. Expected result: Ada (28) — member Grace (34) — admin 5 { bananas: 3, cherries: 8 } function describeUser({ name, age, role = "member" }) destructures the parameter object right in the function definition.

Spreading original into shallowCopy creates new top-level bindings for title and meta, so reassigning shallowCopy.title is safe. But shallowCopy.meta still points to the exact same nested object as original.meta, so mutating a property inside it leaks back into original. Explicitly spreading the nested meta object too (deepCopy) breaks that shared reference for that one level. Expected result: Report 99 Ada shallowCopy.meta.pages = 99; mutates a nested object that both original and shallowCopy still share by reference.

Common mistake: Assuming { ...obj } produces a fully independent "deep" copy — it only copies one level deep; nested objects and arrays remain shared references unless you spread them explicitly too, at every level that needs independence.

const coordinates = [12.5, 45.2];
const [latitude, longitude] = coordinates;
console.log(latitude, longitude);

const user = { name: "Lin", age: 30 };
const { name, age } = user;
console.log(name, age);
function describeUser({ name, age, role = "member" }) {
  return `${name} (${age}) — ${role}`;
}

console.log(describeUser({ name: "Ada", age: 28 }));
console.log(describeUser({ name: "Grace", age: 34, role: "admin" }));

const inventory = { apples: 5, bananas: 3, cherries: 8 };
const { apples, ...otherFruit } = inventory;
console.log(apples);
console.log(otherFruit);
const original = { title: "Report", meta: { author: "Ada", pages: 10 } };

const shallowCopy = { ...original };
shallowCopy.title = "New Title";        // safe — top-level, doesn't affect original
shallowCopy.meta.pages = 99;            // NOT safe — meta is shared by reference

console.log(original.title);            // unaffected
console.log(original.meta.pages);       // affected! shared nested object

const deepCopy = { ...original, meta: { ...original.meta } };
deepCopy.meta.author = "Grace";
console.log(original.meta.author);      // unaffected — meta was copied too

Rest vs spread — same syntax, opposite direction

The ... syntax means opposite things depending on where it appears: gathering (rest) on the left side of a destructuring assignment, expanding (spread) inside a literal or function call.

REST — gathers values INTO a new array/object (used in a pattern, left side)

  const [a, ...rest] = [1, 2, 3, 4];
         │    └─────┬─────┘
         a=1    rest=[2,3,4]  (collected)

SPREAD — expands values OUT of an existing array/object (used in a literal)

  const combined = [...arr1, ...arr2];
                     └─┬─┘   └─┬─┘
                  expanded   expanded
                   in place   in place

Important Rules

Array destructuring unpacks by position; object destructuring unpacks by property name.

Rest syntax (...name) must be the last element in a destructuring pattern and collects all remaining items.

Spread syntax (...value) expands an array or object's contents into a new array/object literal or into function arguments.

Both spread and Object.assign create shallow copies only — nested objects/arrays remain shared by reference between original and copy.

Destructuring a property from undefined or null throws a TypeError; guard with a default parameter or optional chaining when the source might be missing.

Where this shows up

Object destructuring in function parameters is the standard way modern JavaScript and React functions accept configuration or props. Spread is the idiomatic way to update state immutably in React (setUser({ ...user, name: newName })) and to merge configuration objects. Array destructuring is used constantly to unpack values returned as small arrays, such as React's useState() hook: const [value, setValue] = useState(0).

const settings = { ...defaults }; settings.theme.dark = true; // mutates defaults.theme too!

const settings = { ...defaults, theme: { ...defaults.theme, dark: true } }; // fully independent

The spread only copies theme's reference, not its contents, so settings.theme and defaults.theme still point to the same nested object — mutating one nested property affects both. Spreading the nested object explicitly breaks the shared reference.

[Multiple Choice] In const [a, ...rest] = [1, 2, 3, 4], what does rest equal? A. 1 B. [1, 2, 3, 4] C. [2, 3, 4] D. undefined

C. [2, 3, 4] — a takes the first element (1) by position, and the rest pattern collects everything remaining into a new array: [2, 3, 4].

[True / False] Spreading an object with { ...original } creates a fully independent deep copy, safe to mutate at any nesting level without affecting the original. A. True B. False

B. False — False. Spread creates only a shallow copy — top-level properties are independent, but any nested object or array is still shared by reference between the original and the copy.

Given const response = { status: 200, data: { id: 1, name: "Widget" }, headers: { "content-type": "json" } }, write a line using destructuring that extracts status and data directly into variables, and a separate line using spread that creates a copy of response with an added ok: true property, without mutating response.

const { status, data } = response; console.log(status, data); const withOk = { ...response, ok: true }; console.log(withOk); console.log(response.ok); // undefined — original is untouched

Use const { status, data } = response; for the first part, and { ...response, ok: true } for the second.