Last updated: Apr 29, 2026

TypeScript Challenges

Work in Progress — A collection of solutions to TypeScript type challenges sourced from BFE.dev TypeScript, type-challenges, and TypeScript Cookbook.

Key Techniques

TechniqueUse case
Mapped types
[K in keyof T]
Transform every property of an object type (add/remove modifiers, remap values)
keyof / T[number] indexed accessExtract keys from objects or element types from tuples as unions
Conditional types
extends ? :
Branch logic at the type level — pattern match and narrow types
Distributive conditionalsFilter or transform each member of a union independently
infer keywordCapture an unknown type from inside a structure (tuple element, promise inner, function params)
Recursive typesPeel through nested or variable-length structures one layer at a time
Variadic tuple types
...T
Spread, concatenate, or destructure tuple types

Challenges

Pick

Re-implement the built-in Pick<T, K>. Constrain K to valid keys of T, then use a mapped type to iterate over those keys.

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

Concepts: mapped types, keyof, generic constraints.

Readonly

Re-implement the built-in Readonly<T>. Add the readonly modifier to every property via a mapped type.

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P];
};

Concepts: mapped types, readonly modifier.

Tuple to Object

Transform a readonly tuple into an object where each element becomes both key and value. T[number] produces a union of all tuple element types.

type TupleToObject<T extends readonly (string | number | symbol)[]> = {
  [P in T[number]]: P;
};

Concepts: indexed access with T[number], mapped types, PropertyKey constraint.

First of Array

Return the first element’s type, or never for empty arrays. Check length to handle the empty case.

type First<T extends any[]> = T["length"] extends 0 ? never : T[0];

Alternative using infer:

type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;

Concepts: conditional types, tuple length check, infer with rest elements.

Length of Tuple

Access the length property of a tuple type to get a numeric literal. The readonly constraint ensures as const arrays are accepted.

type Length<T extends readonly unknown[]> = T["length"];

Concepts: tuple length property returns a numeric literal (not number).

Exclude ⚡

Re-implement Exclude<T, U>. Conditional types distribute over unions automatically — each member of T is tested against U independently.

type MyExclude<T, U> = T extends U ? never : T;

Concepts: distributive conditional types, union filtering.

Awaited

Recursively unwrap Promise (or any thenable) to get the final resolved type. Uses PromiseLike instead of Promise to handle custom thenables.

type MyAwaited<T> = T extends PromiseLike<infer Inner> ? MyAwaited<Inner> : T;

Concepts: recursive conditional types, infer, PromiseLike for thenable compatibility.

If

Type-level ternary. Constrain C to boolean, then branch on true.

type If<C extends boolean, T, F> = C extends true ? T : F;

When C is boolean (i.e. true | false), the conditional distributes and returns T | F.

Concepts: conditional types, boolean = true | false distribution.

Concat

Use variadic tuple types to spread both arrays into a new one.

type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [
  ...T,
  ...U,
];

Concepts: variadic tuple types, spread in tuple types.

Includes ⚡

Check whether a tuple contains an element with exact equality. Requires recursive iteration — simple extends checks fail on edge cases like boolean vs true, { a: 'A' } vs { readonly a: 'A' }, and union types.

import type { Equal } from "@type-challenges/utils";

type Includes<T extends readonly any[], U> = T extends [
  infer First,
  ...infer Rest,
]
  ? Equal<First, U> extends true
    ? true
    : Includes<Rest, U>
  : false;

Equal performs a strict structural check that extends alone cannot. The recursion peels off one element at a time until a match is found or the tuple is exhausted.

Concepts: recursive conditional types, infer with rest, exact type equality vs assignability.

Push

Append an element to a tuple using spread.

type Push<T extends unknown[], U> = [...T, U];

Concepts: variadic tuple types.

Unshift

Prepend an element to a tuple using spread.

type Unshift<T extends unknown[], U> = [U, ...T];

Concepts: variadic tuple types.

Parameters

Re-implement Parameters<T>. Use infer in the function argument position to capture the parameter tuple.

type MyParameters<T extends (...args: any[]) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;

Concepts: infer in function types, function type constraints.

Append to Object

Add a new key–value pair to an existing object type. Union keyof T with the new key U in the mapped type, then conditionally resolve the value type.

type AppendToObject<T, U extends PropertyKey, V> = {
  [K in keyof T | U]: K extends keyof T ? T[K] : V;
};

The keyof T | U union ensures existing keys keep their original types while U gets value type V.

Concepts: mapped types, union in in clause, conditional value resolution.

Merge

Merge two object types into one, with the second type’s properties overriding the first’s when keys collide.

type Merge<F, S> = {
  [K in keyof F | keyof S]: K extends keyof S
    ? S[K]
    : K extends keyof F
      ? F[K]
      : never;
};

The priority chain S → F → never resolves each key: S wins on overlap, F fills the rest, and never is unreachable since K is constrained to keyof F | keyof S.

Concepts: mapped types, union of keyof, nested conditional types for priority merge.

IsNever

Check whether a type resolves to never. Wrapping in a tuple [T] is essential — bare T extends never distributes over an empty union and always returns true for never after distribution, making the branch unreachable without the tuple wrapper.

type IsNever<T> = [T] extends [never] ? true : false;

Concepts: never as an empty union, distributive conditional types, tuple wrapping to suppress distribution.

AnyOf ⚡

Return true if any element in a tuple is truthy. Define a Falsy union, then recurse through the tuple — if the head isn’t Falsy, short-circuit to true; if the tuple is exhausted, return false.

type Falsy = "" | 0 | false | undefined | null | [];

type AnyOf<T extends unknown[]> = T extends [infer First, ...infer Tail]
  ? First extends Falsy
    ? AnyOf<Tail>
    : true
  : false;

Concepts: recursive conditional types, infer with rest, union membership check, falsy type modeling.

Lookup

Extract a member from a union by matching a discriminant field. Distributing a conditional over the union lets each member check itself against the target literal.

type LookUp<T, K extends string> = T extends { type: K } ? T : never;

Concepts: distributive conditional types, discriminated union filtering.

Last

Return the last element of a tuple. Match a [...Head, Tail] pattern — the variadic head absorbs everything except the final element.

type Last<T extends unknown[]> = T extends [...infer Head, infer Tail]
  ? Tail
  : never;

Concepts: infer with variadic rest in tail position, tuple destructuring.

Capitalize

Uppercase only the first character of a string literal. Split the string into First + Rest with infer, apply the built-in Uppercase utility to First, then reassemble.

type MyCapitalize<S extends string> = S extends `${infer First}${infer Rest}`
  ? `${Uppercase<First>}${Rest}`
  : S;

Concepts: template literal types, infer inside template literals, Uppercase intrinsic.

TrimLeft

Strip leading whitespace (spaces, newlines, tabs) from a string literal. Recurse as long as the first character is a whitespace character.

type TrimLeft<S extends string> = S extends `${infer First}${infer Rest}`
  ? First extends " " | "\n" | "\t"
    ? TrimLeft<Rest>
    : S
  : S;

Concepts: recursive template literal types, character-level infer.

Replace

Replace the first occurrence of From in S with To. Guard against an empty From first, then use a three-part infer split around the target substring.

type Replace<
  S extends string,
  F extends string,
  T extends string,
> = F extends ""
  ? S
  : S extends `${infer Head}${F}${infer Tail}`
    ? `${Head}${T}${Tail}`
    : S;

The empty-string guard is necessary because an empty From would match any string via a template literal split, producing an infinite or ambiguous result.

Concepts: template literal infer with a known middle segment, edge-case guarding.

Deep Readonly ⚡

Recursively apply readonly to every property in a nested object. Check keyof T[K] extends never to detect primitives (functions, scalars) and leave them as-is; recurse into anything with keys.

type DeepReadonly<T> = {
  readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly<T[K]>;
};

Concepts: recursive mapped types, readonly modifier, primitive detection via keyof.

Reverse

Reverse a tuple type. Peel the first element off with infer, recurse on the rest, then append the head at the end.

type Reverse<T extends unknown[]> = T extends [infer First, ...infer Rest]
  ? [...Reverse<Rest>, First]
  : T;

Concepts: recursive variadic tuple types, infer with rest, tail-append pattern.

IsUnion ⚡

Detect whether T is a union by exploiting how distributive conditionals behave. Keep a Copy of the original T, then distribute over T — for each member, check if it covers all of Copy. If yes for every member, T is not a union.

type IsUnion<T, Copy = T> = (
  T extends Copy ? (Copy extends T ? true : false) : false
) extends true
  ? false
  : true;

For a union like string | number, distributing T yields individual members (string, then number). Copy extends T then checks string | number extends string — which is false, so the outer check fails, and IsUnion returns true. For a non-union, both extends checks succeed and the whole expression is true, so IsUnion returns false.

Concepts: distributive conditional types, union detection via distribution, Copy trick to preserve original union.

IsAny ⚡

Detect whether T is any. any uniquely absorbs all intersections — 1 & any collapses to any, making 0 extends 1 & T true only when T is any.

type IsAny<T> = 0 extends 1 & T ? true : false;

For any concrete type T, 1 & T is either 1 (or a subtype) or never, so 0 extends 1 & T is false. Only any makes the intersection widen back to any, satisfying 0 extends any.

Concepts: any type absorption in intersections, structural quirks of any.