jsguides

JavaScript Destructuring: Objects, Arrays, and Patterns

JavaScript destructuring lets you unpack values from arrays or properties from objects into distinct variables using a syntax that mirrors the structure you are extracting from. It arrived in ES6 and quickly became one of the most-used features in modern JavaScript.

Object Destructuring

The most common form works with objects. You wrap the target variables in curly braces and JavaScript matches them by property name:

const user = {
  name: "Alice",
  email: "alice@example.com",
  location: "London"
};

// Extract name and email into their own variables
const { name, email } = user;

console.log(name);  // "Alice"
console.log(email); // "alice@example.com"

Object destructuring pulls properties by matching names, so the variable names on the left must match the object keys on the right. The location property is simply ignored because it was not listed in the pattern. This name-matching behavior keeps the syntax predictable, but it also means you cannot change variable names without extra syntax. When you need the value under a different name, use the colon syntax to assign the property to a new variable:

The variable names must match the object keys. If you need different names, rename them:

const { name: userName, email: userEmail } = user;

console.log(userName);    // "Alice"
console.log(userEmail);  // "alice@example.com"

Renaming during destructuring saves a separate assignment line. When an API returns user_name but your codebase uses userName, the colon syntax handles the translation inline. Arrays, by contrast, are destructured by position rather than property name, which makes them a natural fit for ordered data like coordinates or tuples.

Array Destructuring

Arrays are destructured by position rather than keys. The order matters:

const rgb = [255, 128, 64];

const [red, green, blue] = rgb;

console.log(red);   // 255
console.log(green); // 128
console.log(blue);  // 64

The three variables red, green, and blue each receive the value at the corresponding index. If you declare fewer variables than elements, the extra elements are simply ignored. You can also skip elements you do not need by leaving gaps in the pattern:

Skip elements by leaving gaps:

const [first, , third] = [1, 2, 3];

console.log(first); // 1
console.log(third); // 3

Skipping elements is useful when a function returns a multi-value array and you only need a subset. Node.js callbacks often pass (error, result) pairs where you might only care about the result on success. The same comma-skip syntax works for any position in the array.

Default Values

When a property might be undefined, provide a fallback:

const person = { name: "Bob" };

// Without defaults — role becomes undefined
const { name, role } = person;
console.log(role); // undefined

// With defaults — role gets a default
const { name: n, role = "guest" } = person;
console.log(role); // "guest"

Destructuring with defaults avoids the repetitive options.greeting || "Hello" pattern that clutters function bodies. The default only kicks in when the extracted value is undefined, so null, false, and 0 pass through unchanged. Arrays support the same fallback syntax:

This works for arrays too:

const [x = 0, y = 0, z = 0] = [1, 2];
console.log(z); // 0 (default used)

Array defaults help when working with functions that return variable-length arrays. If String.prototype.match() returns null instead of an array, you can pair a default empty array with element defaults to handle the missing-match case without an if check.

Rest Pattern

Collect remaining properties or elements into a new variable using three dots:

const user = {
  id: 1,
  username: "alice",
  email: "alice@example.com",
  verified: true
};

// Extract id and username, collect the rest
const { id, username, ...details } = user;

console.log(id);       // 1
console.log(username); // "alice"
console.log(details);  // { email: "alice@example.com", verified: true }

The rest pattern collects every property that was not explicitly named. In larger objects where you only need a few specific fields, ...rest is a clean way to separate the data you care about from everything else. The same three-dot syntax works for arrays, but instead of collecting remaining properties by name, it gathers remaining elements by position:

With arrays:

const [first, second, ...rest] = [1, 2, 3, 4, 5];

console.log(first);  // 1
console.log(second); // 2
console.log(rest);   // [3, 4, 5]

Array rest is particularly useful when parsing command-line arguments or splitting a path string, where the first few segments have known meaning and the rest is variable-length content. Unlike object rest, array rest must appear at the end of the pattern. Both forms are available in deeply nested structures too.

Nested Destructuring

Pull values from deeply nested structures:

const config = {
  server: {
    host: "localhost",
    port: 3000
  },
  database: {
    name: "app_db",
    pool: 10
  }
};

// Extract deeply nested values
const { server: { host, port }, database: { name } } = config;

console.log(host); // "localhost"
console.log(port); // 3000
console.log(name); // "app_db"

Nested destructuring mirrors the shape of the data, so the pattern tells you at a glance which fields are being pulled out and from where. Be careful not to nest too deeply, though. If a destructuring pattern stretches across three or more lines, consider extracting intermediate objects first. The same syntax shines when applied to function parameters, where it eliminates boilerplate property access inside the function body:

Destructuring function parameters

Clean up function signatures by destructuring arguments directly:

function greet({ name, greeting = "Hello" }) {
  return `${greeting}, ${name}!`;
}

const message = greet({ name: "Alice", greeting: "Hi" });
console.log(message); // "Hi, Alice!"

// Works with missing properties too
const fallback = greet({ name: "Bob" });
console.log(fallback); // "Hello, Bob!"

Declaring defaults in the function signature keeps the fallback values visible alongside the parameter names. This pattern also works when destructuring specific properties from an event object, which is common in React components and DOM event handlers:

Common use case: destructuring the event object in React or DOM handlers:

function handleClick({ target, currentTarget }) {
  console.log(`Clicked element: ${target.tagName}`);
  console.log(`Handler attached to: ${currentTarget.tagName}`);
}

Destructuring the event object reveals exactly which properties the handler depends on without scanning the function body. This is a small change that makes event handlers easier to review in pull requests. Array destructuring brings a different kind of brevity when you need to reorder values.

Swapping Variables

Array destructuring makes swapping two values trivial:

let a = 1;
let b = 2;

[a, b] = [b, a];

console.log(a); // 2
console.log(b); // 1

The swap works because the right side evaluates the array first, creating a temporary array with the current values of b and a before the assignment runs. No temporary variable is needed, and the pattern scales to swapping three or more values in a single line. While destructuring is concise, it does not prevent every mistake. Here are a few pitfalls that trip up developers new to the syntax.

Common Mistakes

Referencing a non-existent property without a default

const { missing } = {}; // No error, but missing is undefined

This is fine unless you destructure later and expect a value. Always provide a default or add a guard when a property might genuinely be absent from the source object. Another common mistake is mixing up object and array destructuring syntax when the source data is not what you expect.

Forgetting that object destructuring uses keys, not position

const { a, b } = { a: 1, b: 2 }; // Works
const { a, b } = [1, 2]; // Works, but extracts from indices

The second line works because arrays have numeric keys, but it is confusing. Do not do it. Stick to array syntax ([...]) for arrays and object syntax ({...}) for objects. The destructuring pattern should match the shape of the source data so the intent is clear at a glance.

Mutating the original object

Destructuring creates new variables. It does not modify the source:

const original = { count: 0 };
const { count } = original;
count = 5; // TypeError: Assignment to constant variable
// Use let if you need to reassign

Destructuring copies primitive values and object references. For primitives like numbers and strings, you get an independent copy. For objects, you get a reference to the same object, so modifying properties through the destructured variable still affects the original. Keep this in mind when working with nested data structures.

Practical Example

Extracting API response data:

// Typical API response
const response = {
  status: 200,
  data: {
    users: [
      { id: 1, name: "Alice", email: "alice@example.com" },
      { id: 2, name: "Bob", email: "bob@example.com" }
    ],
    pagination: { page: 1, total: 2 }
  },
  timestamp: "2026-03-11T12:00:00Z"
};

// Pull out what you need
const {
  status,
  data: {
    users,
    pagination: { page, total }
  }
} = response;

console.log(`Page ${page} of ${total}, got ${users.length} users`);

Without destructuring, you would write response.data.pagination.page multiple times. With destructuring, you extract once and use page directly.

Why destructuring feels natural

Destructuring mirrors the shape of the data you already have, which is why it reads so well. When you pull properties out of an object or elements out of an array, the assignment line documents what matters right now. That can make a function easier to scan because the names you care about are right at the top instead of buried in repeated property access. In practice, destructuring is not just shorter syntax. It is a way to state the data dependencies of a block of code more directly.

Choosing defaults carefully

Defaults are useful, but they should represent a real fallback rather than a guess. If a property can be missing because the input is optional, a default makes the code friendlier. If the property should always be present, a default can hide a problem that should have been validated earlier. That is why destructuring and validation often appear together. First check the shape of the input, then use defaults to cover the small gaps that are expected in normal use.

Function parameters and options objects

Destructuring function parameters works especially well for “options object” APIs. The call site can name only the settings it wants to change, while the function can keep readable defaults in one place. That pattern scales better than a long positional argument list because the order of options stops mattering. It also makes it easier to add a new setting later without breaking existing callers, which is one of the reasons object-shaped configuration is so common in JavaScript libraries.

Nested data without the noise

Nested destructuring is powerful, but it should stay readable. If you need to reach too deeply into a structure, consider whether a few intermediate variables would make the code clearer. A destructuring statement should help the reader follow the shape of the data, not turn the line into a puzzle. When the nesting is reasonable, though, it is a clean way to express that several values travel together through the same object.

When to use arrays and objects

Use object destructuring when the property names matter more than the position of the values. Use array destructuring when order is the important part. That distinction is the easiest way to choose between them. If you are unpacking a response object, object destructuring usually wins. If you are unpacking coordinates, tuples, or a return value where each slot has a known meaning, array destructuring is often the better fit.

Avoid Over-Destructuring

Destructuring can become harder to read if you pull too many names out at once. A large assignment line can hide the original shape of the data instead of making it clearer. If that starts to happen, split the pattern into smaller steps or keep the original object around alongside a few extracted values. The goal is readability, not cleverness. A good destructuring statement should feel like a summary of the data you care about, not a puzzle the reader has to solve.

Working with unstable shapes

Not every input object is predictable. API responses, query parameters, and user-generated data can all vary a little from call to call. In those cases, destructuring works best when you combine it with guards and defaults. That way the code can tolerate missing optional fields without pretending that every shape is safe. If a structure is too irregular, it may be better to validate first and destructure only after the input has been normalized.

Destructuring and team readability

One of the best reasons to use destructuring is that it helps other people understand what the code expects. The names you pull out become a compact contract for the function or block. That contract is especially useful in code reviews, where a reader can glance at the assignment and immediately see which pieces of data are relevant. When used with restraint, destructuring makes code easier to maintain because the important parts of the shape are visible right where they are needed.

Keep the shape obvious

The best destructuring examples are the ones that still let you see the original structure in your head. If the assignment starts to hide where the data came from, that is a sign to slow down and keep a little more context in view. A named intermediate variable can be the right trade-off when it makes the flow easier to follow. The technique is strongest when it clarifies intent, not when it replaces every property access out of habit. Working with unstable shapes is easier when you combine destructuring with guards and defaults. That way the code can tolerate missing optional fields without pretending that every shape is safe. If the structure is too irregular, validate first and destructure only after the input has been normalized. That split keeps the code honest and makes the fallback behavior easier to reason about.

See Also