Destructuring (Objects and Arrays)

You're reading someone else's React code and see this at the top of a component:

const { user: { name, email }, isLoading = false } = props;

What just happened? If you've ever felt like modern JavaScript is full of mysterious shortcuts that everyone except you understands, destructuring is probably the biggest culprit. Once it clicks, you'll wonder how you ever lived without it.

What is Destructuring?

Destructuring is a syntax that lets you unpack values from arrays or properties from objects into distinct variables — all in a single line.

Instead of accessing properties one by one:

const name = user.name;
const email = user.email;
const age = user.age;

You can extract them all at once:

const { name, email, age } = user;

Think of it as the reverse of building an object. When you create an object, you pack values into a structure. When you destructure, you unpack them.

Why Does This Exist?

Before ES6 (2015), extracting values from objects and arrays was tedious. Developers wrote repetitive code like:

var config = getConfig();
var host = config.host;
var port = config.port;
var timeout = config.timeout;

This got worse with nested objects and function parameters. Destructuring was added to make code more concise and readable — and it became essential for working with React props, API responses, and modern JavaScript patterns.

Let's See It in Action

Basic Object Destructuring

const user = {
  name: "Dima",
  role: "Developer",
  country: "Germany"
};

// Extract specific properties into variables
const { name, role } = user;

console.log(name);  // Output: "Dima"
console.log(role);  // Output: "Developer"
// 'country' wasn't extracted — it's still in 'user' but not a separate variable

The variable names must match the property names exactly. The order doesn't matter — only the names do.

Basic Array Destructuring

const colors = ["red", "green", "blue"];

// Position matters for arrays
const [first, second, third] = colors;

console.log(first);   // Output: "red"
console.log(second);  // Output: "green"
console.log(third);   // Output: "blue"

Unlike objects, arrays are destructured by position, not name. You can name the variables whatever you want.

Skipping Elements in Arrays

const scores = [95, 87, 72, 88, 91];

// Skip the middle values with empty slots
const [highest, , , , lowest] = scores;

console.log(highest);  // Output: 95
console.log(lowest);   // Output: 91

// Or grab just the first and ignore the rest
const [winner] = scores;
console.log(winner);   // Output: 95

Renaming Variables (Object Destructuring)

What if the property name doesn't work for you? Rename it:

const apiResponse = {
  user_name: "dima_codes",
  user_id: 42,
  is_active: true
};

// Rename snake_case to camelCase
const { user_name: username, user_id: userId, is_active: isActive } = apiResponse;

console.log(username);  // Output: "dima_codes"
console.log(userId);    // Output: 42
// user_name is NOT a variable — only username is

The syntax is { originalName: newName }. Read it as "take user_name and call it username."

Default Values

When a property might not exist, provide a fallback:

const settings = {
  theme: "dark"
  // 'language' is missing
};

const { theme, language = "en", notifications = true } = settings;

console.log(theme);         // Output: "dark" (from object)
console.log(language);      // Output: "en" (default, since missing)
console.log(notifications); // Output: true (default, since missing)

Defaults only kick in when the value is undefined, not null:

const { value = "default" } = { value: null };
console.log(value);  // Output: null (NOT "default")

const { other = "default" } = { other: undefined };
console.log(other);  // Output: "default"

Real-World Usage: React Components

Destructuring is everywhere in React:

// Props destructuring in function parameters
function UserCard({ name, avatar, role = "Member", onEdit }) {
  return (
    <div className="card">
      <img src={avatar} alt={name} />
      <h2>{name}</h2>
      <span>{role}</span>
      <button onClick={onEdit}>Edit</button>
    </div>
  );
}

// Usage
<UserCard 
  name="Dima" 
  avatar="/dima.jpg" 
  onEdit={() => console.log("Edit clicked")} 
/>

The component receives a single props object, but destructuring in the function signature makes each prop available directly.

Real-World Usage: API Responses

async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`);
  const { data: user, meta: { totalPages, currentPage } } = await response.json();
  
  // Now we have:
  // - user (renamed from 'data')
  // - totalPages (extracted from nested 'meta')
  // - currentPage (extracted from nested 'meta')
  
  return { user, totalPages, currentPage };
}

Nested Destructuring

You can destructure multiple levels deep:

const company = {
  name: "TechCorp",
  address: {
    city: "Berlin",
    country: "Germany",
    coordinates: {
      lat: 52.52,
      lng: 13.405
    }
  }
};

// Extract nested values
const { 
  name,
  address: { 
    city, 
    coordinates: { lat, lng } 
  } 
} = company;

console.log(name);  // Output: "TechCorp"
console.log(city);  // Output: "Berlin"
console.log(lat);   // Output: 52.52

// Note: 'address' and 'coordinates' are NOT variables here!
// Only the "leaf" values become variables

The Rest Pattern

Grab specific items and collect the rest:

// With arrays
const [gold, silver, ...others] = ["Alice", "Bob", "Charlie", "Diana", "Eve"];
console.log(gold);    // Output: "Alice"
console.log(silver);  // Output: "Bob"
console.log(others);  // Output: ["Charlie", "Diana", "Eve"]

// With objects
const { password, ...safeUser } = {
  name: "Dima",
  email: "dima@example.com",
  password: "secret123"
};
console.log(safeUser);  // Output: { name: "Dima", email: "dima@example.com" }
// password is extracted and excluded from safeUser

This is incredibly useful for removing sensitive fields or separating known properties from dynamic ones.

Watch Out: Destructuring Undefined/Null

// ❌ This will crash
const { name } = undefined;
// TypeError: Cannot destructure property 'name' of 'undefined'

const [first] = null;
// TypeError: null is not iterable

Always ensure the source exists:

// ✅ Safe with default empty object
const { name } = undefined || {};
console.log(name);  // Output: undefined (no crash)

// ✅ Or use optional chaining + nullish coalescing
const data = null;
const { name: userName } = data ?? {};
console.log(userName);  // Output: undefined (no crash)

Watch Out: Variable Declaration Required

// ❌ Syntax error — looks like a block, not destructuring
{ name, age } = user;

// ✅ Wrap in parentheses when assigning to existing variables
let name, age;
({ name, age } = user);

Without const/let/var, JavaScript thinks { starts a code block. Parentheses fix this.

Interview Challenge: Swapping Variables

let a = 1;
let b = 2;

// What does this do?
[a, b] = [b, a];

console.log(a);  // Output: ?
console.log(b);  // Output: ?

Answer: a is now 2, b is now 1. This is a clean way to swap values without a temporary variable. The right side creates an array [2, 1], then destructuring assigns those values back to a and b.

Interview Challenge: What Gets Logged?

const config = {
  api: {
    host: "localhost",
    port: 3000
  }
};

const { api: { host, port }, api } = config;

console.log(host);  // Output: ?
console.log(api);   // Output: ?

Answer: host is "localhost", and api is { host: "localhost", port: 3000 }. You can extract nested properties AND keep a reference to the parent object in the same destructuring. The order doesn't matter.

Advanced: Function Parameter Defaults

Combine destructuring with default parameters for clean APIs:

function createUser({ 
  name, 
  role = "user", 
  active = true,
  permissions = []
} = {}) {  // The = {} handles when no argument is passed at all
  return { name, role, active, permissions };
}

// All of these work:
createUser({ name: "Dima" });
// { name: "Dima", role: "user", active: true, permissions: [] }

createUser({ name: "Admin", role: "admin", permissions: ["delete"] });
// { name: "Admin", role: "admin", active: true, permissions: ["delete"] }

createUser();
// { name: undefined, role: "user", active: true, permissions: [] }

The outer = {} is crucial — without it, calling createUser() with no arguments would crash.

How to Think About This

Think of destructuring as pattern matching against a shape.

For objects, you're saying: "I expect this shape { name, email }, and I want those pieces as variables." The object must have properties matching those names.

For arrays, you're saying: "I expect this shape [first, second, third], and I want those positions as variables." The array must have elements at those indices.

💡 Remember: Objects destructure by name, arrays destructure by position. Defaults apply when the value is undefined, not null.

Test Yourself

Before moving on, make sure you can answer:

  • What's the difference between { x: y } and { x = y } in destructuring?
  • Why does const { a } = null throw an error?
  • How do you rename a property while destructuring AND provide a default value?
  • What does the rest pattern (...) collect in object vs array destructuring?
  • How can you destructure function parameters with defaults?

Related Topics

  • Spread Operator — The mirror image of destructuring; spreads elements out instead of collecting them
  • Default Parameters — Works hand-in-hand with destructuring in function signatures
  • Object Shorthand — When { name: name } becomes just { name }, the opposite of destructuring
  • Optional Chaining — Safely access nested properties without destructuring crashing on undefined

Go Deeper