Editor's note: Mat Marquis and Andy Bell have launched JavaScript for Everyone, an online course available exclusively through Piccalilli. What follows is an excerpt from a chapter dedicated to JavaScript destructuring. We're sharing this material because we think it's valuable and want to encourage you to check out the full course. Consider this a preview of what JavaScript for Everyone has to offer.
I've spent enough time writing about JavaScript that I wouldn't be surprised if I've earned some kind of karmic comeuppance. Over a decade ago, I authored JavaScript for Web Designers during an era when var declarations still dominated the landscape. While the core principles remain solid, some of the guidance has inevitably aged. Yet one particular passage from that book continues to resonate, much to my ongoing frustration.
An entire programming language seemed like too much to ever fully understand, and I was certain that I wasn't tuned for it. I was a developer, sure, but I wasn't a developer-developer. I didn't have the requisite robot brain; I just put borders on things for a living.
JavaScript for Web Designers
This mindset persists among remarkably skilled designers and CSS specialists who somehow can't imagine identifying as "JavaScript developers"—as if they lack some innate ability to grasp concepts like variable hoisting. The irony? Many of them actively write JavaScript in their daily work. While I might not defend every example in that book (looking at you, alert()), its core message remains as relevant now as it was then: type a semicolon and you're writing JavaScript. Write JavaScript and you're a JavaScript developer. Period.
Here's the reality: nobody instinctively thinks in JavaScript, but mastering it requires learning how the language thinks. Understanding why certain intuitive approaches fail while counterintuitive ones succeed means going beyond syntax and execution—you need to understand JavaScript's internal logic. You need to engage with the language on its own terms.
That deeper understanding is what JavaScript for Everyone aims to deliver—a course designed to bridge the gap between junior and senior developer. My goal is to demystify JavaScript's more esoteric rules, explaining not just how but why, using syntax you'll encounter in real-world development. Newcomers will gain foundational knowledge that would otherwise take hundreds of hours of trial and error. Junior developers will emerge with senior-level depth.
Thanks to CSS-Tricks, I can share the complete lesson on destructuring assignment. These are among my favorite JavaScript features—powerful, concise syntaxes that accomplish significant work with minimal characters. That brevity comes with a cost: these patterns can be opaque, especially when you're armed only with MDN documentation and determination. By the end of this lesson, you'll be unpacking complex nested data structures with confidence.
Also available: another excerpt from JavaScript for Everyone covering JavaScript Expressions.
Destructuring Assignment
When working with arrays or object literals, you'll often need to extract values and assign them to individual variables. This makes the data easier to manipulate, but traditionally required verbose code:
const theArray = [ false, true, false ];
const firstElement = theArray[0];
const secondElement = theArray[1];
const thirdElement = theArray[2];
This works—it has for three decades. But ES6 introduced a more elegant solution: destructuring.
Destructuring extracts individual values from arrays or objects and assigns them to identifiers without accessing keys or values one at a time. In its simplest form—binding pattern destructuring—each value is unpacked and assigned to a corresponding identifier, all declared with a single let or const. The syntax takes some getting used to:
const theArray = [ false, true, false ];
const [ firstElement, secondElement, thirdElement ] = theArray;
console.log( firstElement );
// Result: false
console.log( secondElement );
// Result: true
console.log( thirdElement );
// Result: false
Despite the unusual placement of brackets on the left side of the assignment operator, this single binding replaces all the verbose code from the earlier example.
For arrays, identifiers are wrapped in brackets, and each comma-separated identifier receives the value from the corresponding array element. You'll sometimes hear this called unpacking, but despite the terminology, the original data structure remains unchanged.
You can skip elements by omitting identifiers between commas, similar to creating sparse arrays:
const theArray = [ true, false, true ];
const [ firstElement, , thirdElement ] = theArray;
console.log( firstElement );
// Result: true
console.log( thirdElement );
// Result: true
Object destructuring with binding patterns differs slightly. Identifiers are wrapped in curly braces, and in the simplest form, they must match the property keys:
const theObject = {
"theProperty" : true,
"theOtherProperty" : false
};
const { theProperty, theOtherProperty } = theObject;
console.log( theProperty );
// result: true
console.log( theOtherProperty );
// result: false
Arrays are indexed collections where order matters—with destructuring, identifiers correspond to elements sequentially. Objects are keyed collections—essentially unordered property sets accessed by their keys. This isn't problematic in practice; you'd typically want identifiers that match or closely resemble the property keys anyway. The limitation is that this approach assumes a specific structure in the object being destructured.
The alternate syntax looks unusual but offers more flexibility. Set aside what you know about object literals for a moment:
const theObject = {
"theProperty" : true,
"theOtherProperty" : false
};
const { theProperty : theIdentifier, theOtherProperty : theOtherIdentifier } = theObject;
console.log( theIdentifier );
// result: true
console.log( theOtherIdentifier );
// result: false
If you're thinking about object literal notation, this looks bizarre—property references where keys would be, identifiers where values would be.
But this isn't object literal notation. Think of it this way: within the curly braces, you specify the property key you want, followed by a colon, followed by the identifier that should receive that property's value. Multiple properties are comma-separated. After the closing brace comes the assignment operator (=) and the object being destructured. It reads more naturally with practice.
The second approach is assignment pattern destructuring. Here, destructured values are assigned to specific targets—variables declared with let, properties of other objects, or array elements.
For arrays with let variables, assignment pattern destructuring simply adds a step where you declare variables before destructuring:
const theArray = [ true, false ];
let theFirstIdentifier;
let theSecondIdentifier
[ theFirstIdentifier, theSecondIdentifier ] = theArray;
console.log( theFirstIdentifier );
// true
console.log( theSecondIdentifier );
// false
This produces the same result as binding pattern destructuring:
const theArray = [ true, false ];
let [ theFirstIdentifier, theSecondIdentifier ] = theArray;
console.log( theFirstIdentifier );
// true
console.log( theSecondIdentifier );
// false
With binding pattern destructuring, you can use const right from the start:
const theArray = [ true, false ];
const [ theFirstIdentifier, theSecondIdentifier ] = theArray;
console.log( theFirstIdentifier );
// true
console.log( theSecondIdentifier );
// false
But if you want to populate an existing array or object with destructured values, binding pattern destructuring hits a wall. You'll run into a redeclaration error:
// Error
const theArray = [ true, false ];
let theResultArray = [];
let [ theResultArray[1], theResultArray[0] ] = theArray;
// Uncaught SyntaxError: redeclaration of let theResultArray
The problem is that let, const, and var only create variables. The first part of that destructuring line gets interpreted as let theResultArray, which conflicts with the existing declaration.
Assignment pattern destructuring solves this cleanly:
const theArray = [ true, false ];
let theResultArray = [];
[ theResultArray[1], theResultArray[0] ] = theArray;
console.log( theResultArray );
// result: Array [ false, true ]
The same approach works with objects, though you'll need to add parentheses:
const theObject = {
"theProperty" : true,
"theOtherProperty" : false
};
let theProperty;
let theOtherProperty;
({ theProperty, theOtherProperty } = theObject);
console.log( theProperty );
// true
console.log( theOtherProperty );
// false
Those parentheses are necessary because without them, JavaScript interprets the curly braces as a block statement rather than an object literal, triggering a syntax error:
// Error
const theObject = {
"theProperty" : true,
"theOtherProperty" : false
};
let theProperty;
let theOtherProperty;
{ theProperty, theOtherProperty } = theObject;
// Uncaught SyntaxError: expected expression, got '='
So far, this doesn't offer much beyond binding pattern destructuring. We're still using identifiers that match property keys. But you can use any identifier with the alternate syntax:
const theObject = {
"theProperty" : true,
"theOtherProperty" : false
};
let theFirstIdentifier;
let theSecondIdentifier;
({ theProperty: theFirstIdentifier, theOtherProperty: theSecondIdentifier } = theObject);
console.log( theFirstIdentifier );
// true
console.log( theSecondIdentifier );
// false
Here's where assignment pattern destructuring pulls ahead: it accepts any assignment target, not just identifiers:
const theObject = {
"theProperty" : true,
"theOtherProperty" : false
};
let resultObject = {};
({ theProperty : resultObject.resultProp, theOtherProperty : resultObject.otherResultProp } = theObject);
console.log( resultObject );
// result: Object { resultProp: true, otherResultProp: false }
Both syntaxes support default values that apply when an element or property is missing or explicitly undefined:
const theArray = [ true, undefined ];
const [ firstElement, secondElement = "A string.", thirdElement = 100 ] = theArray;
console.log( firstElement );
// Result: true
console.log( secondElement );
// Result: A string.
console.log( thirdElement );
// Result: 100
const theObject = {
"theProperty" : true,
"theOtherProperty" : undefined
};
const { theProperty, theOtherProperty = "A string.", aThirdProperty = 100 } = theObject;
console.log( theProperty );
// Result: true
console.log( theOtherProperty );
// Result: A string.
console.log( aThirdProperty );
// Result: 100
Destructuring really shines when working with nested structures. You could unpack a nested object in two steps:
const theObject = {
"theProperty" : true,
"theNestedObject" : {
"anotherProperty" : true,
"stillOneMoreProp" : "A string."
}
};
const { theProperty, theNestedObject } = theObject;
const { anotherProperty, stillOneMoreProp = "Default string." } = theNestedObject;
console.log( stillOneMoreProp );
// Result: A string.
Or you can do it all at once:
const theObject = {
"theProperty" : true,
"theNestedObject" : {
"anotherProperty" : true,
"stillOneMoreProp" : "A string."
}
};
const { theProperty, theNestedObject : { anotherProperty, stillOneMoreProp } } = theObject;
console.log( stillOneMoreProp );
// Result: A string.
One line transforms a nested object into three accessible constants.
Mixed data structures work just as smoothly:
const theObject = [{
"aProperty" : true,
},{
"anotherProperty" : "A string."
}];
const [{ aProperty }, { anotherProperty }] = theObject;
console.log( anotherProperty );
// Result: A string.
The syntax is dense, even bordering on cryptic. It takes practice to internalize, but once you get it, destructuring becomes an efficient tool for breaking down complex data structures without creating unnecessary intermediate variables.
Rest Properties
Up to now, we've worked with known quantities: extracting specific properties or elements into specific variables. Real-world data structures are rarely that tidy.
In destructuring assignments, an ellipsis followed by an identifier (...theIdentifier) creates a rest property that captures everything you haven't explicitly unpacked. The rest property bundles the remaining elements or properties into the same type of structure you're destructuring:
const theArray = [ false, true, false, true, true, false ];
const [ firstElement, secondElement, ...remainingElements ] = theArray;
console.log( remainingElements );
// Result: Array(4) [ false, true, true, false ]
I usually avoid overly realistic examples to keep things focused, but "convoluted" is exactly what rest properties help us manage. Here's part of the data from the first newsletter I sent when I started this course:
const firstPost = {
"id": "mat-update-1.md",
"slug": "mat-update-1",
"body": "Hey, great to meet you, everybody. I'm Mat — \\"Wilto\\" is good too — and I'm here to teach you JavaScript. Not just what JavaScript is or what JavaScript does, but the *how* and the *why* of JavaScript. The weird stuff. The *deep magic_.\\n\\nWell, okay, I'm not *currently* here to teach you JavaScript, but I will be soon. Right now I'm just getting things in order for the course — planning, outlining, polishing the fancy semicolons that I only take out when I'm having company over, writing like 5,000 words about `this` as a warm-up that completely got away from me, that kind of thing.",
"collection": "emails",
"data": {
"title": "Meet your Instructor",
"pubDate": "2025-05-08T09:55:00.630Z",
"headingSize": "large",
"showUnsubscribeLink": true,
"stream": "javascript-for-everyone"
}
};
That's a lot to wade through. Assume this comes from an external API we don't control. We could work with the object directly, but that gets unwieldy fast when all we need is, say, the newsletter title and body:
const firstPost = {
"id": "mat-update-1.md",
"slug": "mat-update-1",
"body": "Hey, great to meet you, everybody. I'm Mat — \\"Wilto\\" is good too — and I'm here to teach you JavaScript. Not just what JavaScript is or what JavaScript does, but the *how* and the *why* of JavaScript. The weird stuff. The *deep magic_.\\n\\nWell, okay, I'm not *currently* here to teach you JavaScript, but I will be soon. Right now I'm just getting things in order for the course — planning, outlining, polishing the fancy semicolons that I only take out when I'm having company over, writing like 5,000 words about `this` as a warm-up that completely got away from me, that kind of thing.",
"data": {
"title": "Meet your Instructor",
"pubDate": "2025-05-08T09:55:00.630Z",
"headingSize": "large",
"showUnsubscribeLink": true,
"stream": "javascript-for-everyone"
}
};
const { data : { title }, body } = firstPost;
console.log( title );
// Result: Meet your Instructor
console.log( body );
/* Result:
Hey, great to meet you, everybody. I'm Mat — \\"Wilto\\" is good too — and I'm here to teach you JavaScript. Not just what JavaScript is or what JavaScript does, but the *how* and the *why* of JavaScript. The weird stuff. The *deep magic_.
Well, okay, I'm not *currently* here to teach you JavaScript, but I will be soon. Right now I'm just getting things in order for the course — planning, outlining, polishing the fancy semicolons that I only take out when I'm having company over, writing like 5,000 words about `this` as a warm-up that completely got away from me, that kind of thing.
*/
This approach is clean and efficient—just a few dozen characters extract exactly what we need from a complex nested structure. The id and slug properties aren't necessary for publishing, so they're omitted entirely. But that nested data object looks like it might contain additional properties in future posts. While we need firstPost.data.title isolated, we also want an object containing all the remaining firstPost.data properties, regardless of what they turn out to be:
const firstPost = {
"id": "mat-update-1.md",
"slug": "mat-update-1",
"body": "Hey, great to meet you, everybody. I'm Mat — \\"Wilto\\" is good too — and I'm here to teach you JavaScript. Not just what JavaScript is or what JavaScript does, but the *how* and the *why* of JavaScript. The weird stuff. The *deep magic_.\\n\\nWell, okay, I'm not *currently* here to teach you JavaScript, but I will be soon. Right now I'm just getting things in order for the course — planning, outlining, polishing the fancy semicolons that I only take out when I'm having company over, writing like 5,000 words about `this` as a warm-up that completely got away from me, that kind of thing.",
"data": {
"title": "Meet your Instructor",
"pubDate": "2025-05-08T09:55:00.630Z",
"headingSize": "large",
"showUnsubscribeLink": true,
"stream": "javascript-for-everyone"
}
};
const { data : { title, ...metaData }, body } = firstPost;
console.log( title );
// Result: Meet your Instructor
console.log( metaData );
// Result: Object { pubDate: "2025-05-08T09:55:00.630Z", headingSize: "large", showUnsubscribeLink: true, stream: "javascript-for-everyone" }
That's the sweet spot. Now we have a metaData object capturing everything else within the data property of the source object.
Even if the syntax hasn't fully clicked yet, there's something deeply satisfying about the destructuring pattern above. All that work accomplished in a single line. It's concise, elegant—transforming complexity into simplicity. That's what makes JavaScript compelling.
But you might sense it too: a quiet voice asking whether there's an even better approach. For this specific use case, this solution is nearly optimal—but in JavaScript's broader ecosystem, there's almost always room for improvement. If you don't hear that voice yet, you likely will by the end of the course.
Anyone writing JavaScript is a JavaScript developer—no question about it. But the satisfaction of creating order from chaos in just a few keystrokes, combined with the drive to discover better solutions? That's what separates competent developers from exceptional ones.
You can do more than survive with JavaScript—you can master it. Understanding JavaScript down to its core mechanisms, the gears and springs powering the web's interactive layer, opens new possibilities. Deep JavaScript knowledge sharpens all your skills: layout, accessibility, performance, typography. It means fewer moments of "I wonder if it's possible" or "I guess we have to settle for" in your daily work, even if you're not writing the code yourself. Expanding your skillset makes you better and more valuable professionally, regardless of your role.
JavaScript can be challenging to learn—but that's exactly why JavaScript for Everyone exists. You can do this, and there's help available.
Hope to see you there.
JavaScript for Everyone: Destructuring originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.