Ch. 1 · JavaScript

JavaScript Object Destructuring and Defaults

Destructure objects with renamed bindings and defaults, and learn why only undefined triggers a default while null keeps its value.

~2 min readbeginnerupdated Oct 5, 2026

Destructuring reads several properties into local variables in one expression, and defaults fill in missing values. Two details decide correctness: a default applies only when the value is undefined, and a nested pattern throws unless you give its parent a default.

Before you start

You should be comfortable with object literals, property access, and the difference between undefined and null. Every example runs in Node.js or a browser console.

Step-by-step walkthrough

Step 1: Default on missing or undefined

const { pageSize = 20 } = options binds 20 when pageSize is undefined or absent. It does not apply when the property is null, 0, '' or false, because those are real values. Choose the default operator that matches your intent instead of assuming null is treated as missing.

Step 2: Rename to avoid collisions

Use const { id: userId } = row when id would shadow another binding or read ambiguously. Renaming keeps the source property and the local name decoupled, which matters inside functions that already have an id in scope.

Step 3: Guard nested patterns

const { user: { name } } = payload throws if payload.user is missing. Give the parent a default: const { user: { name } = {} } = payload. The inner default protects the destructure even when the whole object is undefined if you also default the top level.

Worked scenario

The parameter defaults to {} so the function is callable with no arguments, and the nested option is guarded.

function connect({ host = 'localhost', port = 5432, opts: { ssl = false } = {} } = {}) {
  return { host, port, ssl };
}
console.log(connect()); // { host: 'localhost', port: 5432, ssl: false }
console.log(connect({ port: 0 })); // { host: 'localhost', port: 0, ssl: false }
JavaScript

Walk through the example

Calling connect() with no argument uses the top-level {} default, so the body receives an object. port: 0 is a defined value, so the 5432 default is skipped — a common source of “why did my zero disappear?” bugs in code that used ||. The nested opts default keeps a missing opts from throwing.

Common mistake

Expecting a default to replace null. const { name = 'anon' } = { name: null } keeps null, which then fails a later name.toUpperCase(). Normalize null explicitly, or use name ?? 'anon' when you truly want both null and undefined treated as missing.

Verify the behavior

Call the function with undefined, null, an empty object, and a partially filled object. Assert that 0 and '' survive and that only undefined triggers defaults. Add a case where the nested parent is missing and confirm the guarded pattern does not throw.

Interview exercise

You receive { data: null } and destructure { data: { items } }. What happens, and how do you fix it?

Answer and reasoning

The nested destructure throws because data is null, and a default on items cannot help: the failure happens one level up, when reading items off null. Fix it by defaulting the parent, const { data: { items } = {} } = payload, or by reading payload.data?.items and falling back afterward. The key is that defaults protect only the exact level they are written on.

Continue learning

Compare this with nullish defaults and read the MDN destructuring reference. Try the JavaScript interview questions.

More in JavaScript

read ✓JavaScript · mid

JavaScript Date and Timezone Pitfalls

Parse and format dates without timezone surprises: zero-based months, date-only versus date-time parsing, and why UTC storage avoids drift.

~3 min readread →
read ✓JavaScript · mid

JavaScript Number Precision and BigInt

Why 0.1 + 0.2 is not 0.3, where safe integer range ends, and when BigInt is the right tool for large identifiers and money.

~2 min readread →
esc