AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Destructuring: Unpacking Objects and Arrays

📚 JavaScript Advanced⏱️ 20 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 20 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Open the IRCTC app to check a train ticket, and behind the scenes the server hands your browser a JavaScript object something like this:

const pnrDetails = {
  pnrNumber: "2841057392",
  trainNumber: "12951",
  trainName: "Mumbai Rajdhani",
  status: "CNF",
  seat: "B4 23"
};

To show this on screen, you need the individual pieces — not the whole object. The way you already know how to pull values out looks like this:

const trainName = pnrDetails.trainName;
const status = pnrDetails.status;
const seat = pnrDetails.seat;

console.log(`${trainName}: ${status}, Seat ${seat}`);
// Mumbai Rajdhani: CNF, Seat B4 23

Three lines that all do the same kind of work: reach into pnrDetails, grab one property, store it in a variable with the same name. Real apps do this dozens of times per object, and the repetition is exactly the kind of pattern JavaScript's designers noticed and built a shortcut for. That shortcut is destructuring:

const { trainName, status, seat } = pnrDetails;

console.log(`${trainName}: ${status}, Seat ${seat}`);
// Mumbai Rajdhani: CNF, Seat B4 23

One line, same three variables, identical output. That curly-brace pattern on the left of = is not an object being created — it is a description of an object being taken apart. This chapter is about learning to read and write that pattern fluently, for both objects and arrays, because once you can, a huge amount of everyday JavaScript — function parameters, imported modules, API responses — suddenly reads more clearly.

The core idea: a pattern that mirrors the shape of the data

Every destructuring statement has the same skeleton: something on the left that looks like the data you're unpacking, and the actual data on the right.

  • If the data is an array, the pattern on the left is written with square brackets: [a, b, c].
  • If the data is an object, the pattern on the left is written with curly braces: { a, b, c }.

JavaScript then matches each name in the pattern to a piece of the source data and creates a new variable holding that value. The two bracket types are not interchangeable stylistic choices — they trigger two genuinely different matching rules, and confusing them is the single most common destructuring mistake. We'll build each one from a concrete example, then contrast them directly.

Array destructuring: matching by position

Arrays don't have named slots — just numbered ones. So array destructuring matches purely by position: the first name in your pattern gets index 0, the second gets index 1, and so on. Suppose you split a departure time string on the colon:

const timeString = "10:30";
const parts = timeString.split(":");
console.log(parts); // ["10", "30"]

const hours = parts[0];
const minutes = parts[1];

Destructuring collapses the last two lines into one, and you can even skip the intermediate parts variable by destructuring the array that split returns directly:

const [hours, minutes] = timeString.split(":");
console.log(hours, minutes); // 10 30

Trace it carefully: split(":") returns the array ["10", "30"]. The pattern [hours, minutes] lines up position-for-position — hours takes index 0 ("10"), minutes takes index 1 ("30"). One detail worth noticing: both values are still strings, because split always returns strings. Destructuring only unpacks values; it never converts their type. If you needed numbers for arithmetic, you'd still write Number(hours) separately.

Because position is all that matters, you can skip elements you don't need by leaving an empty slot in the pattern — just a bare comma:

const raceResults = ["Aditi", "Rohan", "Meera", "Kabir"];
const [gold, , bronze] = raceResults;
console.log(gold, bronze); // Aditi Meera

The empty gap between the two commas still counts as position 1, so "Rohan" is skipped and bronze correctly lands on index 2, "Meera".

Position-based matching also enables a trick that used to require a temporary variable: swapping two values in one line.

let a = 5, b = 10;
[a, b] = [b, a];
console.log(a, b); // 10 5

Read the right-hand side first, since JavaScript evaluates it before assigning anything: [b, a] builds a brand-new temporary array [10, 5] using the old values of a and b. Only after that array exists does the destructuring pattern [a, b] on the left unpack it — a becomes 10, b becomes 5. No spare variable needed.

Finally, array patterns can supply a fallback value for any position that turns out to be undefined:

const [x, y, z = 0] = [3, 7];
console.log(x, y, z); // 3 7 0

The source array only has two elements, so position 2 is undefined. Because the pattern gives z a default of 0, JavaScript uses that default instead of leaving z as undefined. A default only ever kicks in when the matched value is exactly undefined — not for 0, not for "", not for false.

Object destructuring: matching by name, not position

Objects don't have a first, second, or third property in any way JavaScript guarantees you can rely on — what they have is named properties. So object destructuring matches by property name, and the order you write the names in the pattern is irrelevant. This is the sharpest difference from arrays, so look at it directly:

const student = { name: "Ananya Sharma", rollNumber: 23, marks: 92 };

const { marks, name } = student;
console.log(name, marks); // Ananya Sharma 92

Even though the pattern { marks, name } writes marks first, name correctly holds "Ananya Sharma" and marks correctly holds 92. JavaScript is not looking at position at all — it searches student for a property literally called marks, and a property literally called name, wherever they happen to sit in the object. This is the misconception to retire right now if you're coming fresh from array destructuring: writing { marks, name } does not mean "give me the object's first and second properties." It means "give me whatever is stored under the keys marks and name," full stop. You can also destructure only the properties you actually need and ignore the rest entirely — unlike arrays, there's no need to skip with empty commas.

If a name in your pattern doesn't exist as a key on the object, you don't get an error — you get undefined, exactly like accessing a missing property with dot notation would give you:

const { name, city } = student;
console.log(city); // undefined  -- "city" is not a key on student

You can catch that case with the same default-value syntax arrays use:

const { name, city = "Not specified" } = student;
console.log(name, city); // Ananya Sharma Not specified

Sometimes the property's own name isn't the variable name you want in your code — maybe it clashes with another variable, or a more descriptive local name would help. Object patterns let you rename while unpacking, using a colon: propertyName: newVariableName.

const { name: studentName, marks: studentMarks } = student;
console.log(studentName, studentMarks); // Ananya Sharma 92

name and marks here are not new variables — they are the object's actual keys, telling JavaScript which properties to read. studentName and studentMarks are the new variables that actually get created. This trips students up because it reads backwards from a normal assignment; remember the rule as "key on the left of the colon, your variable on the right."

Renaming and defaults combine freely: { city: hometown = "Not specified" } reads a property called city, stores it in a variable called hometown, and falls back to "Not specified" if city is missing.

Nesting: nested data needs nested patterns

Real records are rarely flat. A CBSE result object typically groups subject-wise marks inside their own nested object:

const student2 = {
  name: "Ananya Sharma",
  scores: { math: 95, science: 89 }
};

To reach math or science directly, nest a pattern inside the pattern, matching the exact shape of the data:

const { name, scores: { math, science } } = student2;
console.log(name, math, science); // Ananya Sharma 95 89

Read this outside-in: scores tells JavaScript which property of student2 to look inside; { math, science } is then applied to that inner object, not to student2. Note carefully that scores itself never becomes a variable here — it's consumed purely as a stepping stone into the nested object. If you wanted both the whole scores object and the individual marks, you'd have to destructure it twice, or write scores as a plain (non-nested) name alongside the nested pattern in a separate step.

The rest pattern: collecting what's left over

Sometimes you want one or two specific pieces plus "everything else, whatever it is." Three dots (...) at the end of a pattern collect the remaining elements or properties into a new array or object. This only works as the last item in a pattern.

const marksList = [92, 85, 78, 90, 88];
const [topScore, ...remainingScores] = marksList;

console.log(topScore);          // 92
console.log(remainingScores);   // [85, 78, 90, 88]

topScore takes position 0 as usual; ...remainingScores then scoops up every position after that into a fresh array. The same idea works on objects, collecting leftover properties instead:

const student3 = {
  name: "Ananya Sharma",
  rollNumber: 23,
  marks: 92,
  section: "B"
};

const { name, ...otherDetails } = student3;
console.log(name);          // Ananya Sharma
console.log(otherDetails);  // { rollNumber: 23, marks: 92, section: "B" }

otherDetails is a genuinely new object containing every property of student3 except name, which was pulled out separately. This pattern is common when you want to peel off one or two fields and pass the rest along unchanged to another function.

Destructuring right inside function parameters

The single most common place you'll meet destructuring in real code is a function's parameter list, because it lets a function declare exactly which pieces of an incoming object it actually uses — readable documentation and unpacking in one step.

function printReportCard({ name, marks, section = "A" }) {
  console.log(`${name} (Section ${section}) scored ${marks} marks.`);
}

printReportCard({ name: "Rohan Verma", marks: 81 });
// Rohan Verma (Section A) scored 81 marks.

Trace the call: the object { name: "Rohan Verma", marks: 81 } arrives as the single argument. JavaScript immediately applies the parameter pattern to it, exactly as if you'd written const { name, marks, section = "A" } = argument; as the function's first line. section isn't in the passed object, so its default "A" is used. Nothing about calling this function changed — you still pass one object — but the function body never has to write student.name or student.marks even once.

A misconception worth correcting carefully: destructuring copies values, it doesn't move or link them — except when the value is itself an object

A natural assumption is that a destructured variable stays permanently "connected" to the object it came from, or conversely that destructuring always makes a fully independent, safe copy. Neither is quite right, and the real rule matters a lot in practice. Watch what happens with a plain, primitive value first:

const original = { name: "Ananya Sharma", scores: { math: 95, science: 89 } };

let { name } = original;
name = "Someone Else";
console.log(original.name); // Ananya Sharma  -- unchanged

Reassigning name after destructuring has zero effect on original.name. The string "Ananya Sharma" was copied into a fresh variable, and the two now live independently. That much matches most students' intuition.

Now watch the nested case:

const { scores } = original;
scores.math = 100;
console.log(original.scores.math); // 100  -- changed!

This time, mutating through the destructured variable did change original. Why the difference? Because original.scores is itself an object, and JavaScript never copies objects wholesale during destructuring — it copies a reference (an address pointing at the same object in memory). scores and original.scores end up pointing at the exact same object, so a change made through either name is visible through both. This is called a shallow copy: the top-level values are genuinely copied, but anything nested one level deeper is shared by reference. It is not a special quirk of destructuring — ordinary dot-notation access (const scores = original.scores;) behaves identically. Destructuring just makes it easier to forget you're still holding a reference, because the syntax looks like you extracted an independent piece of data.

Seeing both matching rules side by side

Array: matched by POSITION Object: matched by NAME index 0 "10" index 1 "30" const [hours, minutes] = parts; hours minutes 1st slot -> 1st name, 2nd slot -> 2nd name. Order in the pattern MUST match the data. name: "Ananya" marks: 92 const { marks, name } = student; name marks Pattern written "marks, name" (reversed order) still lands correctly — keys are matched by NAME.

The left panel shows why array order is non-negotiable: swap the names in [hours, minutes] to [minutes, hours] and you silently get the wrong value in each variable, with no error to warn you. The right panel shows the opposite guarantee: however you order the names inside { }, each one finds its own matching key. That's the whole rule in one picture — square brackets care about order, curly braces care about spelling.

Putting several pieces together

Here is one function that uses nested destructuring, a default value, and a function-parameter pattern at the same time — a fairly typical real-world combination:

const examResult = {
  student: "Kabir Singh",
  subjects: { maths: 88, science: 91, english: 79, computerScience: 95 },
  attendance: 96
};

function generateSummary({ student, subjects: { computerScience }, attendance = 75 }) {
  return `${student} scored ${computerScience} in Computer Science (Attendance: ${attendance}%)`;
}

console.log(generateSummary(examResult));
// Kabir Singh scored 95 in Computer Science (Attendance: 96%)

Trace it top to bottom: examResult is passed in as the single argument. student matches the top-level key student ("Kabir Singh"). subjects: { computerScience } steps into the nested subjects object and pulls out just the computerScience key (95) — the other three subjects are never touched. attendance matches the top-level key attendance (96), so its default of 75 is never used at all; that default only exists to protect the function against a record where attendance wasn't recorded.

Check your understanding

  1. Given const colors = ["red", "green", "blue"];, what does const [, secondColor] = colors; store in secondColor?
  2. Given const book = { title: "Discovery of India", pages: 574 };, will const { pages, title } = book; work correctly even though the order is reversed from how the object was written? Explain why, using the word "key."
  3. Predict the exact output:
    const item = { productName: "Notebook", price: 40, inStock: true };
    const { productName: itemName, discount = 0 } = item;
    console.log(itemName, discount);
    
  4. Given const data = { user: { id: 7, address: { city: "Pune", pin: 411001 } } };, write one destructuring statement that pulls city and pin directly into two variables.
  5. After const { address } = data; (using the object from question 4), if you run address.city = "Nagpur";, does data.user.address.city also change? Justify your answer in terms of references versus copies.
  6. Why does const [a, b] = [b, a]; fail to swap two existing variables the way [a, b] = [b, a]; does? (Hint: think about what const does to a variable that already exists.)

Answer key: (1) "green" — the empty first slot skips index 0, so secondColor takes index 1. (2) Yes; object destructuring matches each pattern name against the object's keys by spelling, not by writing order, so pages and title each find their own key regardless of position. (3) Notebook 0productName is renamed to itemName, and since item has no discount key, the default 0 is used. (4) const { user: { address: { city, pin } } } = data;. (5) Yes, it changes — address holds a reference to the same nested object as data.user.address, so mutating a property through address is visible through the original path too; only reassigning address itself (not its properties) would leave data untouched. (6) const on an already-declared variable throws a redeclaration error; the working version omits const/let entirely because it's reusing existing variables, not creating new ones.

Summary

  • Destructuring unpacks values from an array or properties from an object into individual variables in one statement, using a pattern that mirrors the shape of the data.
  • Array patterns ([a, b]) match by position — the order you write names in must match the order of the data. Empty commas skip positions.
  • Object patterns ({ a, b }) match by property name — writing order is irrelevant, and missing keys simply produce undefined rather than an error.
  • Both pattern types support default values with = value, used only when the matched value is exactly undefined.
  • Object patterns support renaming with key: newName; the name left of the colon must be the real property key.
  • Patterns can nest ({ scores: { math } }) to reach directly into nested arrays or objects.
  • A trailing ...name collects whatever wasn't already picked out into a new array or object — and must be the last item in the pattern.
  • Function parameters can be destructured directly, which is the most common place this syntax appears in real code.
  • Destructuring copies top-level primitive values independently, but nested objects/arrays are copied by reference — mutating a nested destructured value still changes the original.
← ES6 Arrow Functions: The Modern Way to Write FunctionsTemplate Literals: String Interpolation Made Easy →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn