Quiz: Destructuring Delights
Are you a maestro of Destructuring?
Or is it your Symphony of Destruction?
This quiz will test your knowledge of Destructuring in JavaScript: from “basic” object syntax to nested destructuring and default values. Plus bonus questions on TypeScript and inline types!
Jump right in to the warmup - prove your Destructuring skills! 👇
Warmup: Objects
What will this code print?
const person = {
name: 'Dan Levy',
location: 'Cape Town',
};
const { name, age } = person;
console.log(`Name: ${name}, Age: ${age}`);
The age
property does not exist on person
, so age
will be undefined
. Definitely not Infinity
😅
This results in:
Name: Dan Levy, Age: undefined
Warmup: Arrays
What will this code do?
const person = [ 'Dan Levy', 'Cape Town' ];
const [ name, origin, age ] = person;
console.log(`Name: ${name}, Age: ${age}`);
The age
variable is not present in the tuple
array, so it will be undefined
.
This results in:
Name: Dan Levy, Age: undefined
Nested Destructuring
How about some nested destructuring?
const person = {
name: { first: 'Dan' },
address: { city: 'Denver' },
};
const {
name: { first },
address: { city },
birth: { place },
} = person;
console.log(
`First: ${first}, City: ${place}`,
);
You have to be careful when accessing nested properties, as the errors can be hard to spot, with messages that are hard to parse. And the messages vary between browsers and other platforms.
In modern Chrome: TypeError: Cannot read properties of undefined (reading 'place')
In Node 20, ReferenceError: place is not defined
In Safari and Bun, TypeError: Right side of assignment cannot be destructured
The solution is to provide default values for nested properties, or refactor to use optional chaining to avoid errors.
Defaults
Now with some defaults, what will this do?
const person = {
name: { first: 'Dan' },
address: { city: 'Denver' },
};
const {
name: { first = 'Unknown' },
birth: { place = 'Unknown' },
} = person;
console.log(
`Hi ${first} from ${place}`,
);
The birth
property does not exist on person
, so the whole object still needs a default, not just the nested property. Basically it’s missing a = {}
default in there.
The way this is written, says “if person.birth
is undefined
, then place
is Unknown
”. But person.birth
is undefined
, so it’s trying to destructure undefined
, which results in an error.
In modern Chrome: `TypeError: Cannot read properties of undefined (reading 'place')`
In Node 20, `ReferenceError: place is not defined`
In Safari and Bun, `TypeError: Right side of assignment cannot be destructured`
Defaults
What will this do?
const person = {
name: { first: 'Dan' },
address: { city: 'Denver' },
};
const {
name: { first = 'Unknown' },
birth: { place = 'Unknown' } = {},
} = person;
console.log(
`Hi ${first} from ${place}`,
);
The birth
property does not exist on person
, so it falls back to an empty object = {}
. This allows the default value to be used.
Function Arguments
Now as function parameters, what will this do?
function displayUser({
name = "Unknown",
age = -1,
} = { place: "Unknown" }) {
console.log(`Hi ${name} from ${place}`);
}
displayUser({ name: "Dan" });
This function extracts name
and age
properties, using defaults if necessary. In this case, adding a place key to the default object has no effect, as it’s not used executing displayUser()
.
We should get an error due to place
not being defined in the destructured fields, or otherwise defined in function scope.
Function Arguments
What will this gem do?
function displayPlace({
name = "N/A",
place = "N/A",
age = -1,
} = { place: "Unknown" }) {
console.log(`${place}`);
}
displayPlace({ name: "Dan" });
displayPlace({ name: "Dan", place: undefined });
displayPlace({ name: "Dan", place: "Joburg" });
The function displayPlace
will ONLY use a default object if no object is passed in. So, the only way to get the { place: "Unknown" }
default is with zero arguments displayPlace()
.
Another notable behavior here is that passing undefined
for place
will cause the default value to be used, a bit similar to JSON.stringify
behavior (ignoring undefined
, recognizing null
).
This results in:
displayPlace() // Unknown
displayPlace({ name: "Dan" }) // N/A
displayPlace({ name: "Dan", place: undefined }) // N/A
Function Arguments
Similar to the previous one… what will happen next?
function displayPlace({
name = "N/A",
place = "N/A",
age = -1,
} = { place: "Unknown" }) {
console.log(`${place}`);
}
displayPlace({ name: "Dan", place: null });
displayPlace({ name: "Dan", place: undefined });
In this case, the place
property is set to null
in the first call, and undefined
in the second. The default value for place
is only used if the whole object is missing or undefined
. Nulls will come through as null
.
TypeScript Ahead
TypeScript Inline Types
Now in TypeScript… what will this do?
function displayPlace(
{
name = 'N/A',
place = 'N/A',
}: {
name: string;
place: string;
age: number;
},
) {
console.log(`${place}`);
}
displayPlace({ name: 'Dan', place: null });
TypeScript will throw an error because the place
property cannot be null.
If you ignore type errors, running the code will print null
to the console.
So there’s 2 answers.
TypeScript: With Assignment
Now in TypeScript… what will this do?
function displayPlace({
name = 'N/A',
place: location = 'N/A',
}: {
name: string;
place: string;
age?: number;
}) {
console.log(`${location}`);
}
displayPlace({ name: 'Dan', place: 'Denver' });
TypeScript will throw an error because there are missing properties & invalid use of null for a string. This is a good example of how TypeScript can help catch errors at compile time.
If you run the code without type checking, null
will be printed to the console.
So there’s 2 answers.
Nested Destructuring in TS
Spot the type error:
function greet({
name: {first = "N/A", last = "N/A"},
birth: {place = "N/A"} = {},
age = -1,
}: {
name: {first?: string, last?: string};
birth: {place?: string};
age: number;
}) {
console.log(`Hi ${first} ${last} from ${place}`);
}
greet({ name: {first: 'Dan'} });
The error is in the greet
function signature. The age
and birth
properties are missing in the passed object. so they should be optional in the type definition.
Even though the birth
property is destructured with a default value, the type definition requires it to be present. In order to mark a property as optional in TypeScript, you should use the ?
operator. Note birth?: number
isn’t the same as birth: number | undefined
.
TypeScript + Assignment
Now with assignment (note the f
, l
and p
variables)
function greet(
{
name: {first: f = "N/A", last: l = "N/A"},
birth: {place: p = "N/A"} = {},
age = -1,
}: {
name: {first?: string, last?: string};
birth?: {place?: string};
age?: number;
}
) {
console.log(`Hi ${f} ${l} from ${place}`);
// What will 👆 do?
}
greet({
name: {first: 'Dan', last: 'Levy'},
birth: {place: 'Cape Town'},
});
Another Error! You’re starting to guess aren’t you?!
It is hard to read layers of destructuring, with defaults, assignment and types!
As soon as place
is reassigned to the p
variable it is no longer defined in the scope of the console.log
statement.
console.log(`Hi ${f} ${l} from ${place}`); // ❌
// to:
console.log(`Hi ${f} ${l} from ${p}`); // ✅
Yike’s! That was a lot of destructuring, defaults, nesting, assignment, and TypeScript! 🤯
I hope you enjoyed it!
If you’re looking for more… Of course you are! Check out my Quiz Collection for more thrills & excitement!