Built-in Utility Types — Reimplemented from Scratch
TL;DR
Every “magic” utility type in TypeScript (Partial, Pick, Record, ReturnType, Awaited, …) is a thin wrapper around the primitives in 01_generics.md, 02_conditional_and_mapped_types.md, and 03_template_literal_types.md. A senior interview will sometimes ask you to reimplement one on the spot — the test is whether you understand the primitives.
Interview Q&A
Q: Reimplement Partial<T>, Required<T>, Readonly<T>.
All three are mapped types with a single modifier:
type MyPartial<T> = { [K in keyof T]?: T[K] }
type MyRequired<T> = { [K in keyof T]-?: T[K] } // -? strips ?
type MyReadonly<T> = { readonly [K in keyof T]: T[K] }
type MyMutable<T> = { -readonly [K in keyof T]: T[K] } // not built-in, but trivial
Q: Reimplement Pick<T, K> and Omit<T, K>.
type MyPick<T, K extends keyof T> = { [P in K]: T[P] }
type MyOmit<T, K extends keyof any> = {
[P in keyof T as P extends K ? never : P]: T[P]
}
Omit uses key remapping with as never to filter out matching keys. (TS’s built-in Omit is equivalent to Pick<T, Exclude<keyof T, K>>.)
Q: Reimplement Record<K, V>.
type MyRecord<K extends keyof any, V> = { [P in K]: V }
keyof any = string | number | symbol — the set of valid index types.
Q: Reimplement Exclude<T, U> and Extract<T, U>.
Both rely on distributive conditional types — they apply to each member of a union independently.
type MyExclude<T, U> = T extends U ? never : T
type MyExtract<T, U> = T extends U ? T : never
type A = MyExclude<'a' | 'b' | 'c', 'a'> // "b" | "c"
type B = MyExtract<'a' | 'b' | 'c', 'a'> // "a"
Q: Reimplement NonNullable<T>.
type MyNonNullable<T> = T extends null | undefined ? never : T
// Or with Exclude:
type MyNonNullable2<T> = Exclude<T, null | undefined>
Strips null | undefined from a union.
Q: Reimplement ReturnType<F>.
infer in the return position:
type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never
type A = MyReturnType<() => number> // number
type B = MyReturnType<(x: string) => boolean> // boolean
type C = MyReturnType<'not a function'> // never
Q: Reimplement Parameters<F>.
type MyParameters<F> = F extends (...args: infer P) => any ? P : never
type A = MyParameters<(x: string, y: number) => void> // [x: string, y: number]
The result is a tuple — preserves names if the original function has labelled tuple parameters.
Q: Reimplement ConstructorParameters<C> and InstanceType<C>.
type MyConstructorParameters<C> =
C extends new (...args: infer P) => any ? P : never
type MyInstanceType<C> =
C extends new (...args: any[]) => infer R ? R : never
class User { constructor(public name: string, public age: number) {} }
type P = MyConstructorParameters<typeof User> // [name: string, age: number]
type I = MyInstanceType<typeof User> // User
new (...) => R is the constructor-signature form (vs (...) => R for plain functions).
Q: Reimplement Awaited<T>.
Recursively unwraps nested promises:
type MyAwaited<T> =
T extends Promise<infer V>
? MyAwaited<V>
: T
type A = MyAwaited<Promise<string>> // string
type B = MyAwaited<Promise<Promise<number>>> // number
type C = MyAwaited<string> // string
The built-in Awaited also handles thenables more carefully.
Q: Reimplement Uppercase, Lowercase, Capitalize, Uncapitalize?
You can’t reimplement these in pure TS — they’re intrinsic types, implemented inside the compiler. You can use them, but not define them yourself.
Q: What does ThisParameterType<F> / OmitThisParameter<F> do?
Extract or strip the this parameter from a function type.
type T = ThisParameterType<(this: User, x: number) => void> // User
type F = OmitThisParameter<(this: User, x: number) => void> // (x: number) => void
Useful for typing Function.prototype.bind and related patterns.
Q: Why is reimplementing these worth knowing?
Three reasons:
- Read library types. Big libraries (TanStack, tRPC, zod) define their own conditional-mapped types. Recognising the pattern means you can read them.
- Build the missing one. TS doesn’t ship
Mutable<T>,DeepPartial<T>,PickByValue<T, V>,OmitByValue<T, V>,Keys<T, V>(filter to keys whose value extends V) — you build those yourself. - Interview signal. Being able to write
type MyPick<T, K extends keyof T> = { [P in K]: T[P] }from memory marks you as someone who understands the type system instead of someone who knows the names of utilities.
Q: Build DeepPartial<T> and DeepReadonly<T>.
type DeepPartial<T> =
T extends (...a: any[]) => any ? T :
T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } :
T
type DeepReadonly<T> =
T extends (...a: any[]) => any ? T :
T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } :
T
Skip functions (don’t make them partial/readonly), recurse on objects, leave primitives alone.
Q: Build PickByValue<T, V> (filter keys whose value type extends V).
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K]
}
interface User { id: number; name: string; active: boolean }
type Strings = PickByValue<User, string> // { name: string }
Same as never filter trick as Omit.
Gotchas / edge cases
Pick<T, 'noSuchKey'>errors at compile time because ofK extends keyof T. Good.Omit<T, 'noSuchKey'>silently does nothing becauseK extends keyof any— wider. There’s a common stricter version:type StrictOmit<T, K extends keyof T> = Omit<T, K>.Partial<T>is shallow. UseDeepPartial<T>for nested.Awaited<T>is recursive —Awaited<Promise<Promise<X>>>isX, notPromise<X>.Parameters<F>returns a labeled tuple type in TS 4.0+, preserving param names.- Iterating
keyof Tskips inherited / non-enumerable keys — same asfor...insemantics at the type level.
What a senior is expected to say
A junior says “I’d use Partial<User>.” A senior says “Partial is just { [K in keyof T]?: T[K] } — and here’s DeepPartial if you need it, here’s PickByValue to filter keys by value type, here’s how I’d build a RequireOnly<T, K> that makes some props required and the rest optional.” The signal is fluency at the primitive level: mapped + conditional + infer + key remapping — not memorising names of utilities.
Cross-references
- Mapped + conditional +
inferprimitives: 02_conditional_and_mapped_types.md - Generics +
keyof+ lookup types: 01_generics.md
Further reading
- TS Handbook — Utility Types
- TS source — utility type definitions in
lib.es5.d.ts(search fortype Partial,type Pick, etc.)