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.
foois the label on one drawer.- Destructuring opens that drawer and takes out its value.
foo: foosmeans: “Take the value from the drawer labelledfoo, but place it in a box labelledfoos.”- 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.
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:
foois the source object property.foosis 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:
responseis an object with afooproperty and astatusproperty.- The annotation
: { foo: IFoo[] }tells TypeScript that the value being destructured must containfoo, andfoomust be an array ofIFoo. foo: itemsextractsresponse.foo.- The extracted value is assigned to the local variable
items. itemshas typeIFoo[], soitems[0].idis allowed and has typenumber.statusis not extracted, but it remains present onresponse.
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;
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
| Syntax | Meaning | Example |
|---|---|---|
const { foo } = bar | Extract bar.foo into a variable named foo. | foo |
const { foo: foos } = bar | Extract bar.foo into a variable named foos. | foos |
const { foo }: { foo: IFoo[] } = bar | Extract foo and require the source object to have foo: IFoo[]. | typed foo |
const { foo: foos }: { foo: IFoo[] } = bar | Rename foo to foos and type the source object. | typed foos |
const foo: IFoo[] = bar.foo | No 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.
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;
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.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.