Use TypeScript's type system to perform computations at the type level.

Type-Level Arithmetic

// Helper types for numbers
type Length<T extends readonly any[]> = T["length"];

type BuildArray<N extends number, T extends any[] = []> = T["length"] extends N
  ? T
  : BuildArray<N, [...T, any]>;

type Add<A extends number, B extends number> = Length<
  [...BuildArray<A>, ...BuildArray<B>]
>;

type Subtract<A extends number, B extends number> = BuildArray<A> extends [
  ...BuildArray<B>,
  ...infer Rest
]
  ? Length<Rest>
  : never;

type Test1 = Add<2, 3>; // 5
type Test2 = Subtract<5, 2>; // 3

String Manipulation

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

type Test = Reverse<"hello">; // "olleh"

type Split<S extends string, D extends string> = S extends `${infer Head}${D}${infer Tail}`
  ? [Head, ...Split<Tail, D>]
  : [S];

type Test2 = Split<"a,b,c", ",">; // ["a", "b", "c"]

Type-Level Lists

type Cons<H, T extends any[]> = [H, ...T];
type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer T] ? T : never;

type List = Cons<1, Cons<2, Cons<3, []>>>;
type First = Head<List>; // 1
type Rest = Tail<List>; // [2, 3]

Type-Level Conditionals

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

type IsEqual<A, B> = A extends B ? (B extends A ? true : false) : false;

type Test1 = If<true, "yes", "no">; // "yes"
type Test2 = IsEqual<string, string>; // true
type Test3 = IsEqual<string, number>; // false

Type-Level Loops

type Map<T extends any[], F> = {
  [K in keyof T]: F extends (x: T[K]) => infer R ? R : never;
};

type Filter<T extends any[], P> = T extends [infer H, ...infer Rest]
  ? H extends P
    ? [H, ...Filter<Rest, P>]
    : Filter<Rest, P>
  : [];

type Numbers = [1, 2, 3, 4, 5];
type Doubled = Map<Numbers, (x: number) => [x, x]>; // [[1,1], [2,2], ...]
type Evens = Filter<Numbers, 2 | 4>; // [2, 4]

Exercises

Exercise 1: String Length

// Create a type that calculates the length of a string
// At the type level
// Example: Length<"hello"> => 5

Exercise 2: Array Operations

// Create type-level functions:
// - Append: Add element to array
// - Prepend: Add element to beginning
// - Concat: Combine two arrays

Exercise 3: Type-Level Math

// Create type-level functions:
// - Multiply: A * B
// - Power: A ^ B
// - Modulo: A % B

Exercise 4: String Operations

// Create type-level functions:
// - Contains: Check if string contains substring
// - Replace: Replace substring in string
// - Trim: Remove whitespace

Exercise 5: Complex Computation

// Create a type that:
// - Takes a string
// - Splits it by spaces
// - Capitalizes each word
// - Joins them back

Best Practices

  1. Keep it simple - Complex type-level code is hard to maintain
  2. Document thoroughly - Explain what types do
  3. Test with examples - Ensure types work correctly
  4. Be aware of limits - TypeScript has recursion and complexity limits
  5. Use for real problems - Don't over-engineer

Common Mistakes

  1. Hitting recursion limits
// ❌ Too deep
type VeryDeep = BuildArray<1000>; // Might fail

// ✅ Limit depth
type Limited = BuildArray<50>; // Safer
  1. Complex type computations
// ❌ Too complex
type VeryComplex = Compute<VeryComplexInput>;

// ✅ Break into steps
type Step1 = FirstStep<Input>;
type Step2 = SecondStep<Step1>;
type Result = FinalStep<Step2>;
  1. Not handling edge cases
// ❌ Assumes non-empty
type Bad<T extends string> = T extends `${infer F}${string}` ? F : never;

// ✅ Handle empty
type Good<T extends string> = T extends ""
  ? never
  : T extends `${infer F}${string}`
  ? F
  : never;

Next Steps

Learn about Advanced Decorators.