Dan Levy's Avatar DanLevy.net

Quiz: Destructuring Delights

Are you a maestro of Destructuring?

Quiz: Destructuring Delights

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! 👇

1. 

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

2. 

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

3. 

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.

4. 

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`

5. 

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.

6. 

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.

7. 

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

8. 

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

9. 

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.

10. 

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.

11. 

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.

12. 

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!

Quiz Score:

Congrats! Quiz completed.

Edit on GitHubGitHub