Back to blog

Get Array Element Type Safely

3 min read

Cover image for Get Array Element Type Safely
Image crafted by robots

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:

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;
}
Note

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: Order is not what we expect.
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:

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: Order is what we expect.
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.

The tuple check avoids distribution issues:

Why Extract works

When a key is provided:

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:

That’s it. Short syntax for the common case, strict-safe behavior for the optional case.

Let's Discuss

Questions or feedback? Send me an email.

Last updated on

Back to blog