Chapter 6.4☕ 18 min read

Destructuring, Spread & Rest Deep Dive

Data ek line mein nikalo, par nested copy se bacho — yeh shallow hai, andar tak nahi jata.

01Array Destructuring: Unpack by Position

Array destructuring lets you unpack values from arrays by position. Instead of writing const first = arr[0], you write const [first] = arr. One line, clean, powerful.

const coords = [17.3850, 78.4867];

// Basic destructuring
const [lat, lng] = coords;
console.log(lat, lng); // 17.385 78.4867

// Skipping elements
const [, lngOnly] = coords;

// Default values
const [a, b, c = 3] = [1, 2];
console.log(c); // 3

// Rest element (MUST be last)
const [first, ...others] = [1, 2, 3, 4];
console.log(first);  // 1
console.log(others); // [2, 3, 4]

// Swapping (no temp variable!)
let x = 10, y = 20;
[x, y] = [y, x];
console.log(x, y); // 20 10
Why swapping works: The right side [y, x] creates a temporary array with current values. The left side [x, y] destructures it back. The entire right side is evaluated before any assignment — so no temp variable needed! This also works with any iterable: const [a, b] = 'hi' gives a = 'h', b = 'i'.
02Object Destructuring: Unpack by Name

Object destructuring unpacks properties by name, not by position. The variable name must match the property name — unless you rename it.

const user = {
  name: 'Sai',
  age: 25,
  address: {
    city: 'Hyderabad',
    zip: '500081'
  }
};

// Basic destructuring
const { name, age } = user;

// Renaming
const { name: userName } = user;
console.log(userName); // 'Sai'

// Default values
const { role = 'developer' } = user;
console.log(role); // 'developer'

// Rename AND default
const { status: userStatus = 'active' } = user;

// Nested destructuring
const { address: { city } } = user;
console.log(city); // 'Hyderabad'
// Note: 'address' is NOT created as a variable here!
Common confusion: In const { name: userName } = user, the colon does NOT mean "name equals userName". It means "take the property name and assign it to variable userName". Think of it as: property : variable. The property name is on the LEFT of the colon, the variable name is on the RIGHT.
03Spread Operator: Shallow Copy Trap

The spread operator ... expands an iterable into individual elements. For arrays: [...arr] creates a copy. For objects: {...obj} creates a copy. But here's the trap — it's always shallow.

// Array spread (copy + merge)
const a = [1, 2];
const b = [3, 4];
const merged = [...a, ...b]; // [1, 2, 3, 4]

// Object spread (copy + override)
const defaults = { theme: 'dark', fontSize: 16 };
const userConfig = { fontSize: 20 };
const finalConfig = { ...defaults, ...userConfig };
console.log(finalConfig); // { theme: 'dark', fontSize: 20 }

// THE SHALLOW COPY TRAP
const original = { name: 'Sai', address: { city: 'Hyd' } };
const copy = { ...original };
copy.name = 'Ali';       // Doesn't affect original
copy.address.city = 'Mum'; // AFFECTS ORIGINAL! Nested object is shared.

console.log(original.address.city); // 'Mum' — BUG!

// Deep clone fix
const deepCopy = structuredClone(original);
deepCopy.address.city = 'Delhi';
console.log(original.address.city); // 'Mum' — Safe ✅
V8 optimization: V8 optimizes spread operations when it can determine the shape of the source object at compile time. {...obj} is faster than Object.assign({}, obj) in V8 because spread doesn't trigger setters on the target, allowing V8 to skip the setter lookup check. This is a measurable difference in hot code paths!
04Rest Pattern in Destructuring

The rest pattern ... in destructuring collects the remaining elements. It's the opposite of spread — spread expands, rest collects. And it must always be last.

// Array Rest
const scores = [90, 85, 70, 60];
const [highest, ...remaining] = scores;
console.log(highest);   // 90
console.log(remaining); // [85, 70, 60]

// Object Rest — Omitting properties
const userData = {
  username: 'sai123',
  email: 'sai@dev.com',
  password: 'secret123',
  token: 'abc'
};

// Remove sensitive data
const { password, token, ...safeData } = userData;
console.log(safeData); // { username: 'sai123', email: 'sai@dev.com' }

// Function parameters
function processUser({ name, ...details }) {
  console.log('Name:', name);
  console.log('Details:', details);
}
processUser({ name: 'Sai', age: 25, role: 'dev' });
Rest vs Spread: They look the same (...) but behave differently based on context. Spread goes on the RIGHT side of = — it expands. Rest goes on the LEFT side of = or in function parameters — it collects. Same syntax, opposite direction. Think: spread = "unpack", rest = "pack what's left".
05Advanced Patterns & Edge Cases

Once you know the basics, these patterns appear everywhere in real codebases — React, Node.js, Angular, you name it.

// Destructuring in for...of loop
const users = [{ name: 'Sai', age: 25 }, { name: 'Ali', age: 30 }];
for (const { name, age } of users) {
  console.log(`${name} is ${age}`);
}

// Tuple pattern (like Go/Rust error handling)
function fetchData() {
  if (Math.random() > 0.5) return [{ id: 1 }, null];
  return [null, new Error('Failed')];
}
const [data, err] = fetchData();

// Computed property names
const prop = 'age';
const user = { name: 'Sai', age: 25 };
const { [prop]: userAge } = user;
console.log(userAge); // 25

// Destructuring null/undefined THROWS!
// const { x } = null; // TypeError: Cannot destructure
// Fix: always default the parent
const { x } = null || {}; // safe, x = undefined
💡 The Tuple Pattern: The [data, error] pattern is becoming very popular in the React ecosystem (especially with libraries like React Query/SWR) to avoid try/catch nesting. It provides a clean way to handle both success and error states without nesting. You'll see this pattern in Angular's signal-based APIs too.

Lo kar liya — Key Points:

  • ✅ Array destructuring extracts by position — supports skipping, defaults, and rest [...rest]
  • ✅ Object destructuring extracts by name — supports renaming ({ name: n }), defaults, nested paths
  • ✅ Spread ({...obj}, [...arr]) creates SHALLOW copies — nested objects are still shared references
  • ✅ Use structuredClone() for true deep copies, or JSON.parse(JSON.stringify()) for JSON-safe data
  • ✅ Rest in destructuring ({ a, ...rest }) collects remaining properties — useful for omitting fields
  • ✅ Destructuring null or undefined throws TypeError — always default the parent: const {x} = obj || {}
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase