Destructuring, Spread & Rest Deep Dive
Data ek line mein nikalo, par nested copy se bacho — yeh shallow hai, andar tak nahi jata.
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
[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'.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!
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.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 ✅
{...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!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' });
...) 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".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
[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, orJSON.parse(JSON.stringify())for JSON-safe data - ✅ Rest in destructuring (
{ a, ...rest }) collects remaining properties — useful for omitting fields - ✅ Destructuring
nullorundefinedthrows TypeError — always default the parent:const {x} = obj || {}
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login