Types you do not control show up everywhere.
API responses. GraphQL codegen. Third-party libs.
And they love this pattern: an array, buried inside a type, sometimes optional.
When that happens, you usually do not want the array type.
You want the element type.
TypeScript makes the simple case easy. Strict mode is where it gets fun.
This post walks through the common approaches and ends with a strict-safe helper you can reuse.
Step 0: Index the array type
If the array property is required, index it with number.
interface interface Store
Store { Store.orders: { id: string; total: number;}[]
orders: { id: string
id: string; total: number
total: number }[];}
type Order = interface Store
Store["orders"][number];type Order = { id: string; total: number;}
Why this works:
Store["orders"]is an array type.- Indexing an array type with
numbergives you the element type.
Use this when the property is always present.
Step 1: Generalize for any array
Extract an element type from any array or tuple with a conditional type.
type type ElementOf<T> = T extends readonly unknown[] ? T[number] : never
ElementOf<function (type parameter) T in type ElementOf<T>
T> = function (type parameter) T in type ElementOf<T>
T extends readonly unknown[] ? function (type parameter) T in type ElementOf<T>
T[number] : never;
type Num = type ElementOf<T> = T extends readonly unknown[] ? T[number] : never
ElementOf<number[]>;type Num = number
type Tuple = type ElementOf<T> = T extends readonly unknown[] ? T[number] : never
ElementOf<[1, 2, 3]>;type Tuple = 3 | 1 | 2
interface interface Store
Store { Store.orders: { id: string; total: number;}[]
orders: { id: string
id: string; total: number
total: number }[];}
type Order = type ElementOf<T> = T extends readonly unknown[] ? T[number] : never
ElementOf<interface Store
Store["orders"]>;type Order = { id: string; total: number;}
Use readonly unknown[] so the utility works with both mutable arrays (T[]) and readonly arrays (readonly T[]).
This covers raw arrays and tuples.
Now let’s point it at a specific property.
Step 2: Target an object property
Add a key parameter so you can point at a specific property.
interface interface Store
Store { Store.orders: { id: string; total: number;}[]
orders: { id: string
id: string; total: number
total: number }[];}
type type ElementOf<T, K extends keyof T> = T[K] extends readonly unknown[] ? T[K][number] : never
ElementOf<function (type parameter) T in type ElementOf<T, K extends keyof T>
T, function (type parameter) K in type ElementOf<T, K extends keyof T>
K extends keyof function (type parameter) T in type ElementOf<T, K extends keyof T>
T> = function (type parameter) T in type ElementOf<T, K extends keyof T>
T[function (type parameter) K in type ElementOf<T, K extends keyof T>
K] extends readonly unknown[] ? function (type parameter) T in type ElementOf<T, K extends keyof T>
T[function (type parameter) K in type ElementOf<T, K extends keyof T>
K][number] : never;
type Order = type ElementOf<T, K extends keyof T> = T[K] extends readonly unknown[] ? T[K][number] : never
ElementOf<interface Store
Store, "orders">;type Order = { id: string; total: number;}
This works while the property type stays a pure array.
Then optional shows up.
Step 3: Handle optional properties in strict mode
With strictNullChecks, optional properties include undefined.
That changes the type.
And it breaks Step 2 because a union like X[] | undefined does not extend readonly unknown[].
interface interface Store
Store { Store.orders?: { id: string; total: number;}[]
orders?: { id: string
id: string; total: number
total: number }[];}
type type ElementOf<T, K extends keyof T> = T[K] extends readonly unknown[] ? T[K][number] : never
ElementOf<function (type parameter) T in type ElementOf<T, K extends keyof T>
T, function (type parameter) K in type ElementOf<T, K extends keyof T>
K extends keyof function (type parameter) T in type ElementOf<T, K extends keyof T>
T> = function (type parameter) T in type ElementOf<T, K extends keyof T>
T[function (type parameter) K in type ElementOf<T, K extends keyof T>
K] extends readonly unknown[] ? function (type parameter) T in type ElementOf<T, K extends keyof T>
T[function (type parameter) K in type ElementOf<T, K extends keyof T>
K][number] : never;
Warning:type Order = type ElementOf<T, K extends keyof T> = T[K] extends readonly unknown[] ? T[K][number] : never
ElementOf<interface Store
Store, "orders">;type Order = never
At this point, Order becomes never.
This is the key idea: you cannot index into a union that includes undefined using this check.
So fix the union first.
Step 4: One helper that covers both cases
This version supports:
- raw arrays:
ElementOf<number[]> - object properties:
ElementOf<Store, "orders"> - optional array properties under
strictNullChecks
interface interface Store
Store { Store.orders?: { id: string; total: number;}[]
orders?: { id: string
id: string; total: number
total: number }[];}
type type ElementOf<T, K extends keyof T = never> = [K] extends [never] ? T extends readonly unknown[] ? T[number] : never : Extract<T[K], readonly unknown[]>[number]
ElementOf<function (type parameter) T in type ElementOf<T, K extends keyof T = never>
T, function (type parameter) K in type ElementOf<T, K extends keyof T = never>
K extends keyof function (type parameter) T in type ElementOf<T, K extends keyof T = never>
T = never> = [function (type parameter) K in type ElementOf<T, K extends keyof T = never>
K] extends [never] ? function (type parameter) T in type ElementOf<T, K extends keyof T = never>
T extends readonly unknown[] ? function (type parameter) T in type ElementOf<T, K extends keyof T = never>
T[number] : never : type Extract<T, U> = T extends U ? T : never
Extract from T those types that are assignable to U
Extract<function (type parameter) T in type ElementOf<T, K extends keyof T = never>
T[function (type parameter) K in type ElementOf<T, K extends keyof T = never>
K], readonly unknown[]>[number];
Message:type Order = type ElementOf<T, K extends keyof T = never> = [K] extends [never] ? T extends readonly unknown[] ? T[number] : never : Extract<T[K], readonly unknown[]>[number]
ElementOf<interface Store
Store, "orders">;type Order = { id: string; total: number;}
Why the mode switch works
K defaults to never.
- If you do not pass a key, you want raw array mode.
- If you pass a key, you want property mode.
The tuple check avoids distribution issues:
[K] extends [never]stays stableK extends nevercan behave badly with unions
Why Extract works
When a key is provided:
T[K]might be{ ... }[] | undefinedExtract<T[K], readonly unknown[]>keeps only the array part- indexing with
[number]returns the element type
That is the whole fix.
Edge cases
For non-array properties, the helper returns never.
interface interface Store
Store { Store.name: string
name: string;}
type Invalid = type ElementOf<T, K extends keyof T = never> = [K] extends [never] ? T extends readonly unknown[] ? T[number] : never : Extract<T[K], readonly unknown[]>[number]
ElementOf<interface Store
Store, "name">;type Invalid = never
If you prefer an error instead of never, you can build a stricter variant later.
Returning never keeps the utility lightweight and composable.
Recap
Use the simplest tool that fits:
- Required array property:
T["prop"][number] - Raw arrays and tuples:
ElementOf<T> - Optional array property:
ElementOf<T, "prop">withExtract
That’s it. Short syntax for the common case, strict-safe behavior for the optional case.