🚀AdvancedType System Mastery

Type-Level Arithmetic & String Parsing

Compute with types at compile time: count tuple lengths, do numeric comparisons, parse string patterns, and build type-safe DSLs.

Overview

TypeScript's type system is Turing-complete. With recursive conditional types and tuple manipulation, you can perform arithmetic and string parsing entirely at the type level. **Numeric types via tuples**: TypeScript does not directly support numeric arithmetic in types, but you can simulate addition, subtraction, and comparison by manipulating tuple lengths. A tuple of length N represents the number N. **String parsing with infer**: Template literal types combined with `infer` allow you to parse string formats character by character or segment by segment, extracting parts into structured types. **Fixed-length arrays**: You can enforce that an array has exactly N elements using tuple types and recursive type helpers. These techniques are used in real libraries: tRPC uses string parsing to extract route parameters; TypeORM uses type arithmetic for query builder chaining; Prisma uses conditional types for relation inclusion.

Key Points

  • •Tuple length as a number type: `[1, 2, 3]["length"]` is `3` (literal type)
  • •Count up using tuples: `type Add<A extends number, B extends number> = [...Tuple<A>, ...Tuple<B>]["length"]`
  • •Tuple from number: build a tuple of length N by recursion
  • •String parsing: `T extends `${infer Head}${infer Tail}`` - split one character at a time
  • •Segment parsing: `T extends `${infer A}/${infer B}`` - split on delimiter
  • •Digit extraction: parse a number string into its individual digits as a union
  • •These techniques have depth limits (~100 recursion levels) - avoid for very large numbers or strings

Examples

Numeric arithmetic via tuple length

// Build a tuple of exactly N elements
type BuildTuple<N extends number, T extends unknown[] = []> =
  T['length'] extends N ? T : BuildTuple<N, [...T, unknown]>;

// Add two numbers
type Add<A extends number, B extends number> =
  [...BuildTuple<A>, ...BuildTuple<B>]['length'];

// Subtract B from A (A >= B assumed)
type Sub<A extends number, B extends number> =
  BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest]
    ? Rest['length']
    : never;

// Numeric comparison: is A less than B?
type LessThan<A extends number, B extends number> =
  BuildTuple<A> extends [...BuildTuple<B>, ...infer _]
    ? false
    : BuildTuple<B> extends [...BuildTuple<A>, ...infer _]
    ? true
    : false;

type Sum2And3  = Add<2, 3>;      // 5
type Diff5And2 = Sub<5, 2>;      // 3
type Is3Lt5    = LessThan<3, 5>; // true
type Is5Lt3    = LessThan<5, 3>; // false

// Fixed-length tuple
type Tuple<N extends number, T = unknown> = BuildTuple<N, []> extends infer R
  ? { [K in keyof R]: T }
  : never;

type Triple<T> = Tuple<3, T>;

const coords: Triple<number> = [1, 2, 3]; // OK
// const bad: Triple<number> = [1, 2];    // Error - must have exactly 3

String splitting and character extraction

// Split a string into a union of characters
type StringToUnion<S extends string> =
  S extends `${infer C}${infer Rest}`
    ? C | StringToUnion<Rest>
    : never;

type Letters = StringToUnion<'hello'>;
// "h" | "e" | "l" | "o"

// Split a string into a tuple of characters
type StringToTuple<S extends string, Acc extends string[] = []> =
  S extends `${infer C}${infer Rest}`
    ? StringToTuple<Rest, [...Acc, C]>
    : Acc;

type HelloChars = StringToTuple<'hello'>;
// ["h", "e", "l", "l", "o"]

// String length at the type level
type StringLength<S extends string> = StringToTuple<S>['length'];

type Len = StringLength<'TypeScript'>; // 10

// Join tuple of strings
type Join<T extends string[], Sep extends string = ''> =
  T extends [infer H extends string, ...infer Rest extends string[]]
    ? Rest['length'] extends 0
      ? H
      : `${H}${Sep}${Join<Rest, Sep>}`
    : '';

type Joined = Join<['Hello', 'World'], ' '>; // "Hello World"

Parsing structured string types

// Parse a path like "/users/:id/posts/:postId" into an object type
type ParseParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? { [K in Param | keyof ParseParams<`/${Rest}`>]: string }
    : T extends `${string}:${infer Param}`
    ? { [K in Param]: string }
    : Record<string, never>;

type UserPostParams = ParseParams<'/users/:userId/posts/:postId'>;
// { userId: string; postId: string }

// Parse a CSV header into a typed record
type CSVRow<Headers extends string> =
  Headers extends `${infer H},${infer Rest}`
    ? { [K in H | keyof CSVRow<Rest>]: string }
    : { [K in Headers]: string };

type UserRow = CSVRow<'id,name,email,age'>;
// { id: string; name: string; email: string; age: string }

// Enforce a pattern at the type level
type SemVer = `${number}.${number}.${number}`;
type ValidVersion = `${number}.${number}.${number}${'' | `-${string}`}`;

function parseVersion(v: ValidVersion): [number, number, number] {
  const [major, minor, patch] = v.split('.').map(Number);
  return [major, minor, patch];
}

parseVersion('1.2.3');     // OK
parseVersion('1.2.3-beta'); // OK
// parseVersion('1.2');    // Error - doesn't match pattern

Common Mistakes

  • Using type-level arithmetic for large numbers: TypeScript's recursion limit (~100 levels) caps what you can compute; `Add<50, 60>` may cause "Type instantiation is excessively deep" errors
  • String parsing on very long strings: each character parsed adds a recursion level; parsing strings longer than ~50 characters risks hitting depth limits
  • Treating type-level computation as zero-cost: complex recursive types increase compile time noticeably; use them sparingly and prefer simpler alternatives when available

Note

Type-level arithmetic is a powerful tool for library authors who need to express constraints like "exactly 3 arguments" or "tuple of the same length as another". For application code, the complexity cost rarely pays off. Use it when the type precision eliminates an entire class of runtime errors that simpler types cannot catch.

Exercises

Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.

Loading editor…

Completed: 0 / 2 exercises

Knowledge Check

Question 1 of 4

How can you represent the number 3 as a type in TypeScript for type-level arithmetic?