JavaScript Destructuring and Spread: Syntax, Rest Patterns, and Worked Examples

Learn how JavaScript destructuring binds values, how rest collects them, and how spread expands them. Follow one object through runnable examples and common traps.

KnowledgeGate Team

Exam prep & CS education

Updated 12 Sep 20265 min read

You may understand const name = student.name and still pause when the same job appears as const { name } = student. The confusion grows because ... can collect or expand values. A single source value can be transformed through array and object patterns, defaults, renaming, rest, spread, and shallow-copy traps.

Destructuring means matching a pattern to a value

Destructuring binds array elements or object properties to variables. Direct access and a pattern can match:

js
const scores = [78, 84, 91];
const first = scores[0];
const [firstAgain] = scores;

console.log(first, firstAgain); // 78 78

Pattern shape controls the lookup. Square brackets follow positions, while curly braces use object keys.

js
const student = { id: 42, name: "Asha", city: "Pune" };
const name = student.name;
const { name: sameName } = student;
const [, secondScore] = scores;
const { city } = student;

console.log(name, sameName); // "Asha" "Asha"
console.log(secondScore, city); // 84 "Pune"

An empty slot skips position 0. The shorthand { city } uses the same property and variable name. This foundation helps you build practical web-development skills.

Object destructuring: rename, default, and go nested

Keep this object unchanged for the main extraction:

js
const student = {
  id: 42,
  name: "Asha",
  city: "Pune",
  scores: [78, 84, 91],
  contact: { email: "asha@example.com" }
};

Rename one property and provide a default:

js
const { name, city: hometown, rank = 1 } = student;

The result is name === "Asha", hometown === "Pune", and rank === 1. In city: hometown, city is the source key and hometown is the variable. The default applies because the missing rank is undefined.

Nested patterns can reach deeper:

js
const { contact: { email } } = student;
console.log(email); // "asha@example.com"

This creates email, not a separate contact variable. A default replaces only undefined, not null:

js
const { points = 0 } = { points: null };
console.log(points); // null

It also works in a parameter:

js
const describe = ({ name, city = "Unknown" }) => `${name} from ${city}`;
console.log(describe(student)); // "Asha from Pune"

Array destructuring and rest complete the extraction

Patterns can cross objects and arrays:

js
const { scores: [first, second, ...remaining] } = student;

Here, first is 78, second is 84, and remaining is the new array [91]. One statement handles everything:

js
const {
  id,
  name,
  city: hometown,
  scores: [first, second, ...remaining],
  contact: { email },
  rank = 1
} = student;

console.log(id, name, hometown, first, second, remaining, email, rank);
// 42 "Asha" "Pune" 78 84 [91] "asha@example.com" 1

Inside a binding pattern, rest means "collect what is left". Object rest follows the same rule:

js
const { id: studentId, contact, ...summary } = student;

Now studentId === 42, contact.email === "asha@example.com", and summary exactly equals { name: "Asha", city: "Pune", scores: [78, 84, 91] }. Excluded keys do not appear.

Binding map linking the student object fields to variables id, name, hometown, first, second, remaining, email, and a default rank.

Spread expands values into a new container or argument list

Context decides what ... means. Rest collects in a binding pattern. Spread expands in a literal or function call.

js
const extendedScores = [...student.scores, 96];
const highest = Math.max(...student.scores);

console.log(extendedScores); // [78, 84, 91, 96]
console.log(highest); // 91

With object spread, later properties win:

js
const updatedStudent = { ...student, city: "Delhi" };
const restoredStudent = { city: "Delhi", ...student };

updatedStudent.city is "Delhi", but student.city stays "Pune". In reverse order, the later spread restores the original city, so restoredStudent.city is "Pune".

For an immutable array update, copy both changed containers:

js
const revisedStudent = {
  ...student,
  scores: [...student.scores, 96]
};

revisedStudent.scores is [78, 84, 91, 96], while student.scores remains [78, 84, 91]. You can apply this in a full-stack project when updating state without changing its source.

The shallow-copy trap and four failures to recognise

Spread creates a new outer object, not recursive nested copies:

js
const clone = { ...student };
clone.contact.email = "asha.new@example.com";
console.log(student.contact.email);
// "asha.new@example.com"

Both outer objects initially point to the same contact. Copy that level when it must change independently:

js
const safeClone = {
  ...student,
  contact: { ...student.contact, email: "safe@example.com" }
};

Now safeClone.contact.email is "safe@example.com" without another change to student.contact.email.

Two snapshots: clone shares the contact object with student, while safeClone copies contact separately but still shares other nested references.

Failure or trap

What happens

Fix or rule

const { name } = undefined

It throws a TypeError because there is no object to inspect.

Use const { name = "Guest" } = maybeStudent ?? {};.

const [head, ...tail, last] = [10, 20, 30]

The pattern is invalid.

Rest must be the last element.

{ name } = student;

A bare block is parsed incorrectly for this assignment.

Wrap it: ({ name } = student);.

const { score = 50 } = { score: null };

score becomes null, not 50.

Defaults replace undefined, not null.

How coding tests and interviews use this syntax

Common tasks ask you to predict output, repair a pattern, or transform data without mutation. A broader Coding Round Strategy for Placements helps you explain the reason, not just the output.

Consider overwrite order:

js
const base = { theme: "light", fontSize: 14 };
const choice = { fontSize: 16, compact: true };
const settings = { ...base, ...choice };

The result is { theme: "light", fontSize: 16, compact: true }. choice is spread later, so its fontSize value wins.

Predict the output for each expression and identify whether each use of ... is rest or spread:

  1. What do head and tail contain in const [head, ...tail] = [10, 20, 30, 40]? Is ... rest or spread?

  2. What does const sum = (a, b, c) => a + b + c; sum(...[4, 7, 9]) return? Is ... rest or spread?

For the first, head === 10 and tail is [20, 30, 40]. The pattern collects remaining elements, so ...tail is rest. For the second, 4 + 7 + 9 = 20. The call expands the array into three arguments, so ... is spread.

As a cross-language follow-up, Java String Handling: String Pool and Immutability shows how simple-looking syntax can also hide important value and reference behaviour.

The short version and next step

Use object destructuring when names matter and array destructuring when positions matter. Use rest to collect the remainder, and spread to expand values into a new literal or argument list. Remember the two traps: defaults cover undefined, and spread makes only a shallow copy.

Spend 15 minutes on this exact sequence:

  1. Rewrite three direct property reads from student as one object pattern.

  2. Extract 78, 84, and [91] in one nested pattern.

  3. Append 96 without changing the original scores array.

  4. Make an independent update to the nested contact object.

Run every snippet and inspect both the new value and the original. Continue the language sequence with the Complete JavaScript course. Then add a second nested field such as contact.phone = "9876543210", predict exactly which references must be copied, and only then run the code.