Pick is tiny enough to fit on one line, but it teaches one of the most reusable patterns in the TypeScript type system: constrain a set of keys, iterate over those keys, and rebuild a new object type from the original.
That is why the Pick challenge from the Type Challenges collection is worth slowing down for. If you understand this one well, later utility-type problems stop feeling like syntax tricks and start feeling like variations on the same mental model.
In this article, we will answer three questions:
- What is the challenge actually asking for?
- Why does
K extends keyof Tmatter so much? - How does
[P in K]: T[P]rebuild the final object type?
1. Start with what the challenge is really testing
The original example is short:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
The goal is straightforward: Todo has three properties, but MyPick<Todo, 'title' | 'completed'> should keep only two of them.
The tests make that expectation more precise:
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
// @ts-expect-error
MyPick<Todo, 'title' | 'completed' | 'invalid'>,
]
interface Todo {
title: string
description: string
completed: boolean
}
interface Expected1 {
title: string
}
interface Expected2 {
title: string
completed: boolean
}
So the challenge is not only "copy some properties." It is checking three separate behaviors:
- one valid key should produce an object with one matching property
- multiple valid keys should produce an object with only those properties
- invalid keys should fail at the type level instead of being silently accepted
That last rule is the one many first attempts miss.
2. The full solution is only one line
Here is the complete implementation:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
There are only two moving parts:
K extends keyof T[P in K]: T[P]
If those two pieces feel obvious, Pick feels easy.
If they do not, the challenge becomes a perfect entry point into keyof and mapped types.
3. Why K extends keyof T is the constraint that makes the solution correct
Start with keyof.
If T is Todo, then:
type TodoKeys = keyof Todo
becomes:
type TodoKeys = 'title' | 'description' | 'completed'
That union represents every real property name on Todo.
Now look at the generic constraint:
K extends keyof T
This means:
whatever
Kis, it must be a subset of the keys that already exist onT
So these are valid:
MyPick<Todo, 'title'>
MyPick<Todo, 'title' | 'completed'>
But this is not:
MyPick<Todo, 'title' | 'completed' | 'invalid'>
because 'invalid' is not part of keyof Todo.
That is important for two reasons.
First, it matches the challenge requirement: nonexistent keys must be rejected.
Second, it makes the implementation type-safe for the compiler itself. Inside the mapped type, TypeScript needs to trust that each iterated key really belongs to T. Without the extends keyof T constraint, T[P] would not be guaranteed to make sense.
So K extends keyof T is not decoration. It is the rule that turns "some random generic" into "a safe subset of real property names."
4. How [P in K]: T[P] rebuilds the new object type
Once K is known to contain only valid keys, the rest is a mapped type:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
Read it like this:
- take every key
PinsideK - create a property with that same name
- look up its value type from
T
That is exactly what "pick these properties from an object type" means.
For example:
type Result = MyPick<Todo, 'title' | 'completed'>
The mapped type expands conceptually into:
type Result = {
title: Todo['title']
completed: Todo['completed']
}
And because:
type Todo = {
title: string
description: string
completed: boolean
}
the final result becomes:
type Result = {
title: string
completed: boolean
}
That matches Expected2 exactly.
The single-key case works the same way:
type Result = MyPick<Todo, 'title'>
becomes:
type Result = {
title: Todo['title']
}
which becomes:
type Result = {
title: string
}
That matches Expected1.
5. The easiest mistakes to make when first writing Pick
The final answer is small, but there are a few easy wrong turns:
Using keyof T instead of K
If you write this:
type WrongPick<T, K extends keyof T> = {
[P in keyof T]: T[P]
}
you are no longer picking selected keys.
You are copying every property from T, which defeats the whole purpose.
Forgetting the generic constraint
If K is not constrained by keyof T, then invalid keys are no longer blocked correctly, and the compiler loses the guarantee that T[P] is safe.
Treating mapped types like runtime loops
[P in K] looks a bit like a loop, but it exists only in the type system.
Nothing is iterating at runtime.
This is purely a compile-time transformation from one type shape into another.
6. The real pattern to remember for later utility types
The most useful takeaway from Pick is not the answer itself. It is the reusable shape behind it:
type NewType<T, K extends keyof T> = {
[P in K]: T[P]
}
That pattern appears again and again in beginner and intermediate TypeScript exercises:
- constrain a key set with
keyof - iterate over the selected keys with a mapped type
- rebuild a new object type from the original
Many built-in and challenge-style utility types are just variations on that idea.
Pickkeeps the selected keys and their original value typesReadonlykeeps the keys but changes property modifiersPartialkeeps the keys but makes them optionalRecordcreates keys from a supplied union instead of selecting them from an existing object
Once you see Pick as "select keys, then remap them," the rest of the utility-type family becomes much easier to reason about.
7. One small challenge, one durable mental model
Pick is a beginner-friendly challenge because the final code is short, but it still forces you to understand three important ideas together:
keyofgives you the valid keys of a typeextendsconstrains generics so invalid keys fail early- mapped types let you rebuild an object shape from selected keys
That is the full logic behind:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
If this pattern feels clear now, you are already standing on the foundation for many later TypeScript type challenges.