Skip to content
DevDepth

In-depth React internals and CSS layout guides for frontend engineers.

← Back to all articles

In-Depth Article

How to Implement `First` in TypeScript: `infer`, Tuple Pattern Matching, and `never`

Solve the Type Challenges `First` problem by learning how `readonly [infer F, ...infer _]` extracts the first tuple element, why `readonly unknown[]` constrains the input, and when `T[0]` is a useful alternative.

Published: Updated: 4 min readtypescript
typescriptconditional-typesinfertuple-typestype-challenges

First is a small Type Challenges problem, but it teaches a very reusable TypeScript idea: you can pattern-match a tuple type and infer just the part you need.

If you have already worked through how to implement Pick in TypeScript, how to implement Readonly in TypeScript, and how to implement Tuple to Object in TypeScript, this is a nice next step. Those problems focus on keys, mapped types, or tuple values becoming unions. First is about destructuring a tuple shape directly in the type system.

In this article, we will answer four practical questions:

  • What exactly is the challenge checking?
  • Why should T be constrained to readonly unknown[]?
  • How does readonly [infer F, ...infer _] extract the first element type?
  • Why does an empty tuple return never?

1. Start with what the challenge is really asking for

The original example is short:

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // 'a'
type head2 = First<arr2> // 3

That already tells us the whole goal.

This challenge is not about reading a runtime array value. It is about taking a tuple or array type and returning the type of its first position.

So:

  • First<['a', 'b', 'c']> should be 'a'
  • First<[3, 2, 1]> should be 3

The tests make the behavior more precise:

type cases = [
  First<[3, 2, 1]>,
  First<[() => 123, { a: string }]>,
  First<[]>,
  First<[undefined]>,
]

type errors = [
  // @ts-expect-error
  First<'notArray'>,
  // @ts-expect-error
  First<{ 0: 'arrayLike' }>,
]

Those cases are checking four things:

  • a normal tuple should return the first literal type
  • the first item can be any full type, including a function type
  • an empty tuple should return never
  • non-array inputs should be rejected by the generic constraint

That last point matters because the solution is not only about extracting a type. It is also about preventing invalid input.

2. The complete solution is short

Here is a clean implementation:

type First<T extends readonly unknown[]> =
  T extends readonly [infer F, ...infer _] ? F : never

There are two important parts:

  • T extends readonly unknown[]
  • T extends readonly [infer F, ...infer _] ? F : never

The first part narrows what counts as a valid input. The second part checks whether the tuple is non-empty and, if so, infers the first element type.

3. Why the generic constraint matters

Start with this part:

T extends readonly unknown[]

It means T must be some array or tuple type.

That is why these should fail:

First<'notArray'>
First<{ 0: 'arrayLike' }>

Neither of those inputs is actually an array or tuple, so the constraint blocks them before the conditional logic even runs.

Using readonly unknown[] is slightly more flexible than unknown[], because it also accepts readonly tuples created by as const.

For example:

const values = [1, 2, 3] as const

The type of values is:

readonly [1, 2, 3]

If your helper only accepted mutable arrays, that common tuple shape would no longer fit cleanly.

4. How infer extracts the first element type

Now look at the core pattern:

T extends readonly [infer F, ...infer _] ? F : never

Read it like this:

  • if T can be matched as a tuple with at least one element
  • infer the first element type as F
  • ignore the rest of the tuple
  • return F

This is type-level tuple destructuring.

Take this example:

type A = First<[3, 2, 1]>

The tuple [3, 2, 1] matches:

readonly [infer F, ...infer _]

so F becomes 3, and the final result is:

type A = 3

The same logic works when the first element is a function type:

type B = First<[() => 123, { a: string }]>

Here the first position is () => 123, so F is inferred as that exact function type:

type B = () => 123

That is an important detail. The helper does not transform the first item. It simply returns the full type found at index 0.

5. Why the empty tuple case becomes never

Now consider the empty tuple:

type C = First<[]>

An empty tuple cannot match this pattern:

readonly [infer F, ...infer _]

because that pattern requires at least one element.

So the condition fails, and TypeScript takes the false branch:

type C = never

That matches the challenge exactly.

never is the right result here because there is no first element type to return. This is different from undefined. undefined would mean "there is a value here and its type is undefined." But never means "there is no valid value to extract at all."

6. Why readonly appears in both places

You will often see a shorter version like this:

type First<T extends readonly unknown[]> =
  T extends [infer F, ...infer _] ? F : never

That works for plain tuples in the challenge tests, but the fully readonly version is more consistent:

type First<T extends readonly unknown[]> =
  T extends readonly [infer F, ...infer _] ? F : never

The reason is simple. If the input is a readonly tuple, the pattern should also be written as readonly.

That keeps the helper aligned with common as const inputs and avoids mixing a readonly constraint with a mutable tuple pattern.

7. The T[0] alternative is also worth knowing

You will also see an indexed-access version:

type First<T extends readonly unknown[]> =
  T extends readonly [] ? never : T[0]

This approach is valid too. It handles the empty tuple case explicitly, then uses indexed access to read the first element type.

Compared with the infer version, the tradeoff is mostly about teaching style:

  • the infer version shows how tuple pattern matching works
  • the T[0] version shows how indexed access works on array-like types

For this particular challenge, the infer version is often easier to read because the "first element plus the rest" structure is visible in one place.

8. Two beginner mistakes are especially common

Forgetting the array constraint

If T is unconstrained, invalid inputs such as strings or array-like objects are no longer rejected at the type boundary.

Treating never like undefined

For First<[]>, the result should be never, not undefined. The point is not that the first element exists and happens to be undefined. The point is that there is no first element type to extract.

9. One line of code, one reusable pattern

The final answer is still:

type First<T extends readonly unknown[]> =
  T extends readonly [infer F, ...infer _] ? F : never

But behind that one line are three TypeScript ideas worth keeping:

  • constrain the generic so only real array or tuple inputs are accepted
  • use tuple pattern matching to check whether the input is non-empty
  • infer the first piece you care about and ignore the rest

Once that mental model feels natural, a lot of later Type Challenges problems start looking much less mysterious.

Publisher and editor

DevDepth Publisher

Independent publisher and frontend engineering writer

DevDepth is maintained as an independent frontend engineering publication focused on React internals, CSS layout systems, and production debugging guides.

React internalsCSS layout systemsfrontend debuggingrendering and performance reasoning

Each article is reviewed before publication for technical accuracy, explanation quality, metadata clarity, and internal-link fit within the current archive.

Last editorial review: Mar 24, 2026