Question
Extract Interface Property Types in TypeScript with Indexed Access Types
Question
Suppose a TypeScript declaration file for library X contains these interfaces:
interface I1 {
x: any;
}
interface I2 {
y: {
a: I1;
b: I1;
c: I1;
};
z: any;
}
When working with this library, I need to pass around an object with the same type as the y property of I2. I could duplicate that shape in my own interface:
interface MyInterface {
a: I1;
b: I1;
c: I1;
}
let myVar: MyInterface;
However, this duplicates library types and requires maintenance if the library changes. Is there a way to extract the type of a specific interface property directly, similar to typeof I2.y? A solution using a declared value works, but I want to do this without declaring a redundant variable:
declare const value: I2;
let y: typeof value.y;
Short Answer
TypeScript can extract a property type directly from an interface with an indexed access type. Write I2["y"] to reuse the type of I2's y property without creating a duplicate interface or a placeholder variable. This keeps your code synchronized when the library updates its type definitions.
Concept
An interface is a type, not a runtime JavaScript value. That is why this does not work:
// Invalid: I2 is a type, not an object available at runtime.
type Y = typeof I2.y;
The typeof operator in a type position gets the type of a value that exists (or is declared) as a value. For example, if value is a variable, typeof value produces its type.
To look up a member of a type directly, TypeScript provides indexed access types. They use square brackets:
type Y = I2["y"];
I2["y"] means: “get the type associated with the y key in I2.” It is the type-level equivalent of reading object["y"] at runtime.
This matters because library declarations often contain nested object shapes, callback signatures, configuration objects, and API response types. Reusing a property type avoids copying it into your project, so changes made by the library are automatically reflected in your code when you update its typings.
Mental Model
Think of an interface as a blueprint with labeled rooms.
I2is the complete blueprint.yis one room inside that blueprint.I2["y"]asks TypeScript to give you the blueprint for that one room.
You do not need to build a fake house (declare const value: I2) just to inspect a room. Indexed access lets you refer to the room directly from the blueprint.
Syntax and Examples
Use this syntax to extract a property type:
type PropertyType = InterfaceName["propertyName"];
For the interfaces in the question:
interface I1 {
x: any;
}
interface I2 {
y: {
a: I1;
b: I1;
c: I1;
};
z: any;
}
type I2Y = I2["y"];
const value: I2Y = {
a: { x: "first" },
b: { x: 2 },
c: { x: true }
};
I2Y is equivalent to the object type declared for I2.y:
{
: ;
: ;
: ;
}
Step by Step Execution
Consider this code:
interface User {
id: string;
profile: {
displayName: string;
email: string;
};
}
type Profile = User["profile"];
const profile: Profile = {
displayName: "Ada",
email: "ada@example.com"
};
Step by step:
-
Userdefines an object type withidandprofileproperties. -
User["profile"]performs a type lookup using the key"profile". -
TypeScript finds the declared type of
profileinsideUser. -
Profilebecomes:
Real World Use Cases
Indexed access types are useful whenever one type is the source of truth and you need one part of it elsewhere.
-
Library configuration: Reuse one section of a third-party library's options type.
type DatabaseOptions = AppOptions["database"]; -
API data: Extract the type of a nested API response field.
type UserRecord = ApiResponse["user"]; -
Event handlers: Reuse a callback type from an interface.
type OnSave = EditorOptions["onSave"]; -
Component props: Share the type of a nested prop between UI components.
type ButtonStyle = Theme["button"]; -
State models: Extract one slice of application state.
= [];
Real Codebase Usage
In production code, give extracted types meaningful aliases rather than repeatedly writing long lookups:
type RequestHeaders = HttpRequest["headers"];
type RequestBody = HttpRequest["body"];
This makes function signatures easier to read:
function validateHeaders(headers: RequestHeaders): boolean {
return headers.authorization !== undefined;
}
Indexed access is also commonly combined with generics. A reusable helper can return the value type for any valid object key:
type ValueOf<T, K extends keyof T> = T[K];
interface Settings {
retryCount: number;
endpoint: string;
}
type Endpoint = ValueOf<Settings, "endpoint">;
// string
Common Mistakes
Using typeof with an interface name
This is invalid:
interface I2 {
y: string;
}
type Y = typeof I2.y;
I2 is erased when TypeScript becomes JavaScript. It is not a runtime object, so typeof has no value to inspect. Use I2["y"] instead.
Using dot notation in a type lookup
This is not TypeScript's indexed-access syntax:
// Invalid
type Y = I2.y;
Use square brackets and a key:
type Y = I2["y"];
Misspelling a property key
TypeScript rejects keys that do not exist:
// Error: Property 'missing' does not exist on type 'I2'.
type = [];
Comparisons
| Tool or syntax | What it does | When to use it |
|---|---|---|
I2["y"] | Extracts the type of property y from type I2 | You have a type/interface and need one of its property types |
typeof value | Gets the type of a runtime value or declared variable | You already have a value, function, class, or constant |
keyof I2 | Produces a union of keys, such as "y" | "z" | You need valid property names |
I2[keyof I2] | Produces a union of all property value types | You need any value type from an object type |
I2["y"]["a"] | Extracts a nested property type |
Cheat Sheet
// Extract one property type
type Y = I2["y"];
// Extract a nested property type
type A = I2["y"]["a"];
// Extract types for multiple keys
type Values = I2["y" | "z"];
// Get every key
type Keys = keyof I2;
// Get a union of every property value type
type AllValues = I2[keyof I2];
// Generic property lookup
type ValueOf<T, K extends keyof T> = T[K];
// Get an array element type
type Item = Response["items"][number];
Rules to remember:
- Use
Type["key"], notType.key. - The key must exist on the type.
typeofis for values; indexed access is for properties of types.
FAQ
How do I get the type of an interface property in TypeScript?
Use an indexed access type:
type PropertyType = MyInterface["propertyName"];
For the question's example, use type I2Y = I2["y"];.
Why does typeof I2.y not work?
I2 is an interface, which exists only in TypeScript's type system. It is not a runtime JavaScript object. typeof in a type annotation needs a value to inspect.
Can I extract a nested property type?
Yes. Chain indexed accesses:
type AType = I2["y"]["a"];
Do I need to declare a variable first?
No. declare const value: I2; type Y = typeof value.y; works, but type Y = I2["y"]; is direct and clearer.
Can I use a property name stored in a type alias?
Yes, as long as it is a valid key:
= ;
Y = [];
Mini Project
Description
Build a small type-safe formatter for a library-style API response. You will define the response once and derive the nested user and item types from it, rather than duplicating those shapes in function signatures.
Goal
Create functions that accept extracted nested types and format a user summary and order total.
Requirements
Define an ApiResponse interface with user and orders properties.
Derive a User type from the user property.
Derive an Order type from an element of the orders array.
Write a function that formats a user summary.
Write a function that calculates the total from an array of orders.
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.