Question

TypeScript Types in Object Destructuring

typescriptdestructuring

Question

In TypeScript, the following destructuring attempts produce errors because IFoo[] and Array<IFoo> are written where a binding name is expected:

const { foo: IFoo[] } = bar;
const { foo: Array<IFoo> } = bar;

However, this code is valid because TFoo is treated as a new variable name, not a type:

const { foo: TFoo } = bar;

How can you correctly specify the type of an object property while destructuring it?

Short Answer

You will learn how TypeScript types object destructuring, why : means property renaming inside a destructuring pattern, and how to add a type annotation to the entire destructured object. You will also see how inference, aliases, defaults, and reusable object types work together.

Concept

In object destructuring, TypeScript uses the same syntax as JavaScript. Inside the braces, a colon means rename this property while extracting it.

const { foo: localName } = bar;

This reads the foo property from bar and stores its value in a new variable called localName.

Because of that rule, TypeScript cannot interpret this as a type annotation:

// Invalid: IFoo[] is not a valid variable name.
const { foo: IFoo[] } = bar;

To add a type, annotate the whole destructuring declaration instead:

const { foo }: { foo: IFoo[] } = bar;

Here, { foo: IFoo[] } describes the required shape of bar: it must have a foo property whose value is an array of IFoo values.

This matters because destructuring is common when working with API responses, configuration objects, function parameters, state objects, and database records. Correct annotations let TypeScript validate the incoming object before your code uses its extracted values.

Mental Model

Think of an object as a labelled storage cabinet.

  • foo is the label on one drawer.
  • Destructuring opens that drawer and takes out its value.
  • foo: foos means: “Take the value from the drawer labelled foo, but place it in a box labelled foos.”
  • A type annotation describes what the cabinet must contain, so it is placed after the destructuring pattern.
const { foo: foos }: { foo: IFoo[] } = bar;

The left side chooses and optionally renames drawers. The type after : describes the cabinet being opened.

Take Quiz

Syntax and Examples

Use this general form:

const { propertyName }: { propertyName: PropertyType } = object;

For a property that is an array of IFoo objects:

interface IFoo {
  id: number;
  name: string;
}

const bar = {
  foo: [
    { id: 1, name: "Ada" },
    { id: 2, name: "Grace" }
  ]
};

const { foo }: { foo: IFoo[] } = bar;

console.log(foo[0].name); // Ada

foo is inferred as IFoo[] from the annotation.

Array<IFoo> is equivalent to IFoo[], so this also works:

const { foo }: { foo: Array<IFoo> } = bar;

To rename the extracted variable and type it at the same time:

const { foo: foos }: { foo: IFoo[] } = bar;

console.log(foos.length); // 2

In this example:

  • foo is the source object property.
  • foos is the new local variable.
  • { foo: IFoo[] } is the type of the source object.

Step by Step Execution

Consider this code:

interface IFoo {
  id: number;
}

const response = {
  foo: [{ id: 101 }],
  status: "ok"
};

const { foo: items }: { foo: IFoo[] } = response;

console.log(items[0].id);

Step by step:

  1. response is an object with a foo property and a status property.
  2. The annotation : { foo: IFoo[] } tells TypeScript that the value being destructured must contain foo, and foo must be an array of IFoo.
  3. foo: items extracts response.foo.
  4. The extracted value is assigned to the local variable items.
  5. items has type IFoo[], so items[0].id is allowed and has type number.
  6. status is not extracted, but it remains present on response.

The equivalent non-destructured code is:

const items: IFoo[] = response.foo;

Real World Use Cases

Object destructuring with types is useful whenever a function or value provides a structured object.

  • API responses: Extract an array from a JSON response.

    const { users }: { users: User[] } = apiResponse;
    
  • Application configuration: Read a configuration value with a known type.

    const { port }: { port: number } = config;
    
  • Function results: Extract values returned by a service.

    const { token, expiresAt }: { token: string; expiresAt: Date } = session;
    
  • UI component properties: Pull fields from a props object.

    const { title, isOpen }: { title: string; isOpen: boolean } = props;
    
  • Database query results: Extract records from an object returned by a repository.

    const { rows }: { rows: Order[] } = queryResult;
    
Take Quiz

Real Codebase Usage

In real TypeScript projects, developers usually type the object at its source and let destructuring infer property types.

interface ApiResult {
  foo: IFoo[];
  total: number;
}

function loadResult(): ApiResult {
  return {
    foo: [{ id: 1, name: "Ada" }],
    total: 1
  };
}

const result = loadResult();
const { foo, total } = result;

Because result is an ApiResult, TypeScript already knows that foo is IFoo[]. This avoids repeating types.

A common function-parameter pattern is to type the destructured parameter:

interface Options {
  retryCount: number;
  verbose: boolean;
}

function startJob({ retryCount, verbose }: Options): void {
  if (verbose) {
    console.log(`Retry count: ${retryCount}`);
  }
}

For optional data, developers commonly use a default value:

interface ResponseData {
  foo?: IFoo[];
}

function processResponse({ foo = [] }: ResponseData): void {
  console.log(foo.length);
}

The default makes foo an IFoo[] inside the function, even when the property is absent.

Common Mistakes

Treating the destructuring colon as a type annotation

// Incorrect: `items` is interpreted as a variable name,
// not as a type.
const { foo: IFoo[] } = bar;

Inside { ... }, : performs renaming. Put the type after the full destructuring pattern.

const { foo }: { foo: IFoo[] } = bar;

Accidentally renaming a property to a type-like name

const { foo: TFoo } = bar;

This is valid JavaScript and TypeScript, but TFoo is a runtime variable. By convention, names beginning with I or T often represent types, so using them as variables is confusing.

const { foo: foos }: { foo: IFoo[] } = bar;

Typing only one property when more properties are needed

const { foo, total }: { foo: IFoo[] } = bar;

This fails because the declared type does not say that total exists. Include every destructured property:

const { foo, total }: { foo: IFoo[]; total: number } = bar;

Or use a named type:

interface Result {
  foo: IFoo[];
  total: number;
}

const { foo, total }: Result = bar;

Ignoring optional properties

interface Result {
  foo?: IFoo[];
}

const { foo }: Result = bar;
console.log(foo.length); // Error: foo may be undefined

Use a default value or check first:

const { foo = [] }: Result = bar;
console.log(foo.length);

Over-annotating values that TypeScript already understands

If bar is already typed, this is often unnecessary:

const { foo }: { foo: IFoo[] } = bar;

Prefer inference when it remains clear:

const { foo } = bar;

Comparisons

SyntaxMeaningExample
const { foo } = barExtract bar.foo into a variable named foo.foo
const { foo: foos } = barExtract bar.foo into a variable named foos.foos
const { foo }: { foo: IFoo[] } = barExtract foo and require the source object to have foo: IFoo[].typed foo
const { foo: foos }: { foo: IFoo[] } = barRename foo to foos and type the source object.typed foos
const foo: IFoo[] = bar.fooNo destructuring; type the extracted property directly.typed foo

IFoo[] and Array<IFoo> represent the same array type in TypeScript:

let first: IFoo[] = [];
let second: Array<IFoo> = [];

For simple array types, IFoo[] is often easier to read. Array<T> can be useful for complex generic types, but both are valid.

Take Quiz

Cheat Sheet

// Extract a property
const { foo } = bar;

// Rename a property while extracting it
const { foo: foos } = bar;

// Type the source object being destructured
const { foo }: { foo: IFoo[] } = bar;

// Rename and type
const { foo: foos }: { foo: IFoo[] } = bar;

// Type multiple properties
const { foo, total }: { foo: IFoo[]; total: number } = bar;

// Prefer a reusable named type for repeated shapes
interface Result {
  foo: IFoo[];
  total: number;
}
const { foo, total }: Result = bar;

// Handle an optional property with a default
const { foo = [] }: { foo?: IFoo[] } = bar;

Key rule: within an object destructuring pattern, property: name means property aliasing, not property: type.

FAQ

Can I write a type directly after a destructured property?

No. In object destructuring, foo: something always means “store foo in a local variable named something.” Type the enclosing object instead.

How do I type a renamed destructured property in TypeScript?

Use both the alias and an object type annotation:

const { foo: foos }: { foo: IFoo[] } = bar;

Is IFoo[] different from Array<IFoo>?

No. Both describe an array containing IFoo values.

Do I need an annotation if bar is already typed?

Usually not. TypeScript infers the types of destructured properties from bar.

const bar: { foo: IFoo[] } = getBar();
const { foo } = bar;

How do I destructure an optional typed property safely?

Provide a default value or check for undefined.

const { foo = [] }: { foo?: IFoo[] } = bar;

Can I use a type alias or interface instead of an inline object type?

Yes, and it is usually clearer when the object shape is reused.

type Bar = { foo: IFoo[] };
const { foo }: Bar = bar;
Take Quiz

Mini Project

Description

Create a small function that processes a typed API-style response. It demonstrates object destructuring, property renaming, a reusable interface, and a default value for an optional property.

Goal

Extract and print product data from a response object while keeping all destructured values correctly typed.

Requirements

Define a Product interface with id, name, and price properties.

Define an interface for a response containing products, total, and an optional nextPage.

Destructure products into a local variable named items.

Provide a default value for nextPage.

Print the total, page value, and each product name.

Take Quiz

Keep learning

Related questions

View more