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 `Tuple to Object` in TypeScript: `T[number]`, `PropertyKey`, and Mapped Types

Solve the Type Challenges `Tuple to Object` problem by learning how `T[number]` extracts tuple element types, why `readonly PropertyKey[]` blocks invalid keys, and how mapped types turn each item into both key and value.

Published: Updated: 5 min readtypescript
typescriptmapped-typestuple-typesindexed-access-typestype-challenges

Tuple to Object looks almost too small to be interesting, but it introduces a pattern that shows up again and again in TypeScript: take a union of literal types and feed that union into a mapped type.

If you have already worked through how to implement Pick in TypeScript and how to implement Readonly in TypeScript, this challenge is a useful next step. Those problems iterate over object keys. This one starts from a tuple, extracts its element types, and then rebuilds an object from those values.

In this article, we will answer four practical questions:

  • What is the challenge actually asking for?
  • Why does T[number] turn a tuple into a union of its element types?
  • Why does the constraint need to be readonly PropertyKey[]?
  • Why does as const matter so much for the final result?

1. Start with what the challenge is really checking

The original example already shows the whole goal:

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type result = TupleToObject<typeof tuple>
// expected {
//   tesla: 'tesla'
//   'model 3': 'model 3'
//   'model X': 'model X'
//   'model Y': 'model Y'
// }

Every tuple item becomes both the key and the value in the resulting object type.

So this is not "convert an array into some object shape." It is more specific:

  • take every element from the tuple
  • use that element as an object key
  • use that same element again as the value type

The tests make the target clearer because they also cover numbers, symbol, and an invalid case:

const tupleNumber = [1, 2, 3, 4] as const
const sym1 = Symbol(1)
const sym2 = Symbol(2)
const tupleSymbol = [sym1, sym2] as const
const tupleMix = [1, '2', 3, '4', sym1] as const

type cases = [
  TupleToObject<typeof tupleNumber>,
  TupleToObject<typeof tupleSymbol>,
  TupleToObject<typeof tupleMix>,
]

// @ts-expect-error
type error = TupleToObject<[[1, 2], {}]>

That tells us something important about the valid key space up front:

  • strings are allowed
  • numbers are allowed
  • symbol values are allowed
  • arrays and objects are not allowed

That is exactly the set of types represented by PropertyKey.

2. The full solution is only one line

Here is the whole implementation:

type TupleToObject<T extends readonly PropertyKey[]> = {
  [K in T[number]]: K
}

The code is short because two TypeScript features do almost all of the work:

  • T[number] extracts every element type from the tuple
  • [K in T[number]]: K maps each extracted element into a property

If those two pieces click, the whole challenge becomes straightforward.

3. T[number] is the step that unlocks the problem

This part is the real heart of the challenge:

T[number]

When T is a tuple type, indexing it with number means "give me the union of every element type in this tuple."

For example:

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type CarTuple = typeof tuple

CarTuple is not string[]. Because of as const, it is the readonly tuple:

readonly ['tesla', 'model 3', 'model X', 'model Y']

Now apply indexed access:

type Keys = CarTuple[number]

and you get:

type Keys = 'tesla' | 'model 3' | 'model X' | 'model Y'

That union is exactly what we need. Once the tuple elements have been turned into a union, the rest of the problem becomes a standard mapped type.

4. How the mapped type turns that union into an object

Now look at the second half of the solution:

[K in T[number]]: K

Read it like this:

  • iterate over every member of the union produced by T[number]
  • create a property whose key is that member
  • make the value type the same member again

So for this tuple:

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type Result = TupleToObject<typeof tuple>

the mapped type conceptually expands to:

type Result = {
  tesla: 'tesla'
  'model 3': 'model 3'
  'model X': 'model X'
  'model Y': 'model Y'
}

The same logic works for numeric literals too:

const tupleNumber = [1, 2, 3, 4] as const

type NumberObject = TupleToObject<typeof tupleNumber>

which becomes:

type NumberObject = {
  1: 1
  2: 2
  3: 3
  4: 4
}

And it also works for symbol literals:

const sym1 = Symbol(1)
const sym2 = Symbol(2)
const tupleSymbol = [sym1, sym2] as const

type SymbolObject = TupleToObject<typeof tupleSymbol>

which becomes:

type SymbolObject = {
  [sym1]: typeof sym1
  [sym2]: typeof sym2
}

So the challenge is really just one clean pipeline:

  • tuple
  • union of tuple element types via T[number]
  • mapped type over that union
  • final object where each element is both key and value

5. Why T must extend readonly PropertyKey[]

This generic constraint is doing two jobs at once:

type TupleToObject<T extends readonly PropertyKey[]> = {
  [K in T[number]]: K
}

First, PropertyKey restricts the tuple elements to valid object-key types:

type PropertyKey = string | number | symbol

That is why this error case is rejected:

type error = TupleToObject<[[1, 2], {}]>

[1, 2] is not a valid object key type, and {} is not a valid object key type either, so the constraint correctly blocks them.

Second, the readonly part matters because as const produces a readonly tuple:

const tuple = ['tesla', 'model 3'] as const

The type of tuple is:

readonly ['tesla', 'model 3']

If the generic were written like this instead:

type TupleToObject<T extends PropertyKey[]> = {
  [K in T[number]]: K
}

then typeof tuple would not satisfy the constraint, because a readonly tuple is not assignable to a mutable array type.

So readonly PropertyKey[] is not just a stylistic improvement. It matches the actual input shape that the challenge uses.

6. Why as const matters more than many beginners expect

Without as const, the tuple in the example does not stay a literal tuple type:

const tuple = ['tesla', 'model 3', 'model X', 'model Y']

Now typeof tuple becomes string[], not:

readonly ['tesla', 'model 3', 'model X', 'model Y']

That changes everything.

If T is string[], then:

T[number]

is just string, not the literal union of each individual item.

So the result becomes a broad string-keyed object type rather than the precise object the challenge wants.

That is why as const matters here. It preserves:

  • literal element values
  • tuple length
  • readonly tuple behavior

All three are useful, but the literal element types are the most important part for this challenge.

7. Two common mistakes are worth avoiding

Using keyof T instead of T[number]

If you iterate over keyof T, you are no longer iterating over the tuple values. You are iterating over tuple indices and array members, which is not what the challenge asks for.

This problem wants the element values from the tuple, not the positions of those elements.

Forgetting that duplicate tuple values collapse in the final object

Because T[number] produces a union, duplicate values do not stay duplicated:

type Keys = ['a', 'a'][number] // 'a'

And because object types cannot have two separate properties with the same key, duplicates naturally collapse into one property in the final mapped type.

That behavior is usually fine, but it is helpful to remember that the intermediate step is a union, not a list.

8. One line of code, one reusable mental model

The final answer is still:

type TupleToObject<T extends readonly PropertyKey[]> = {
  [K in T[number]]: K
}

But behind that one line are three ideas that show up everywhere in TypeScript:

  • tuples can preserve literal values when you use as const
  • T[number] turns those tuple element types into a union
  • mapped types can rebuild an object from that union

Once that mental model is clear, Tuple to Object stops feeling like a trick question and starts feeling like a natural combination of indexed access types and mapped types.

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