TypeScript Advanced Types
Advanced TypeScript Types
TypeScript's advanced type system allows you to model complex type relationships with precision.
These features are particularly useful for building robust, maintainable applications with excellent type safety.
Key Advanced Type Features
- Mapped Types: Transform properties of existing types
- Conditional Types: Create types based on conditions
- Template Literal Types: Build types using string templates
- Utility Types: Built-in type helpers for common transformations
- Recursive Types: Self-referential types for tree-like structures
- Type Guards & Type Predicates: Runtime type checking
- Type Inference: Advanced pattern matching with
infer
Mapped Types
Mapped types allow you to create new types by transforming properties of existing types.
Basic Mapped Type
Transform every property of an object type into a new type using a single template.
// Convert all properties to boolean
type Flags<T> = {
[K in keyof T]: boolean;
};
interface User {
id: number;
name: string;
email: string;
}
type UserFlags = Flags<User>;
// Equivalent to:
// {
// id: boolean;
// name: boolean;
// email: boolean;
// }
Try it Yourself »
Mapped Type Modifiers
Add or remove property modifiers like readonly
and ?
across all keys.
// Make all properties optional
interface Todo {
title: string;
description: string;
completed: boolean;
}
type OptionalTodo = {
[K in keyof Todo]?: Todo[K];
};
// Remove 'readonly' and '?' modifiers
type Concrete<T> = {
-readonly [K in keyof T]-?: T[K];
};
// Add 'readonly' and 'required' to all properties
type ReadonlyRequired<T> = {
+readonly [K in keyof T]-?: T[K];
};
Try it Yourself »
Key Remapping
Rename or filter keys while mapping using as
, string helpers, and conditional checks.
// Add prefix to all property names
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// {
// getId: () => number;
// getName: () => string;
// getEmail: () => string;
// }
// Filter out properties
type MethodsOnly<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};
Try it Yourself »
Conditional Types
Conditional types allow you to define types that depend on a condition.
Basic Conditional Types
Select between types based on a condition checked at the type level.
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<'hello'>; // true
type D = IsString<string | number>; // boolean
// Extract array element type
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Numbers = ArrayElement<number[]>; // number
Try it Yourself »
Infer Keyword
Capture a part of a type within a conditional type by introducing a new type variable with infer
.
// Get return type of a function
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// Get parameter types as a tuple
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
// Get constructor parameter types
type ConstructorParameters<T extends new (...args: any) => any> =
T extends new (...args: infer P) => any ? P : never;
// Get instance type from a constructor
type InstanceType<T extends new (...args: any) => any> =
T extends new (...args: any) => infer R ? R : any;
Try it Yourself »
Distributed Conditional Types
Understand how conditionals distribute over unions versus when they are wrapped to prevent distribution.
// Without distribution
type ToArrayNonDist<T> = T extends any ? T[] : never;
type StrOrNumArr = ToArrayNonDist<string | number>; // (string | number)[]
// With distribution
type ToArray<T> = [T] extends [any] ? T[] : never;
type StrOrNumArr2 = ToArray<string | number>; // string[] | number[]
// Filter out non-string types
type FilterStrings<T> = T extends string ? T : never;
type Letters = FilterStrings<'a' | 'b' | 1 | 2 | 'c'>; // 'a' | 'b' | 'c'
Try it Yourself »
Template Literal Types
Template literal types allow you to build types using template literal syntax.
Basic Template Literal Types
Constrain strings to specific patterns using template literals and unions.
type Greeting = `Hello, ${string}`;
const validGreeting: Greeting = 'Hello, World!';
const invalidGreeting: Greeting = 'Hi there!'; // Error
// With unions
type Color = 'red' | 'green' | 'blue';
type Size = 'small' | 'medium' | 'large';
type Style = `${Color}-${Size}`;
// 'red-small' | 'red-medium' | 'red-large' |
// 'green-small' | 'green-medium' | 'green-large' |
// 'blue-small' | 'blue-medium' | 'blue-large'
Try it Yourself »
String Manipulation Types
Apply built-in helpers to transform string literal types (uppercasing, capitalizing, etc.).
// Built-in string manipulation types
type T1 = Uppercase<'hello'>; // 'HELLO'
type T2 = Lowercase<'WORLD'>; // 'world'
type T3 = Capitalize<'typescript'>; // 'Typescript'
type T4 = Uncapitalize<'TypeScript'>; // 'typeScript'
// Create an event handler type
type EventType = 'click' | 'change' | 'keydown';
type EventHandler = `on${Capitalize<EventType>}`;
// 'onClick' | 'onChange' | 'onKeydown'
Try it Yourself »
Advanced Patterns
Compose templates with inference and key remapping to extract metadata and generate APIs.
// Extract route parameters
type ExtractRouteParams<T> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractRouteParams<`${Rest}`>]: string }
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type Params = ExtractRouteParams<'/users/:userId/posts/:postId'>;
// { userId: string; postId: string; }
// Create a type-safe event emitter
type EventMap = {
click: { x: number; y: number };
change: string;
keydown: { key: string; code: number };
};
type EventHandlers = {
[K in keyof EventMap as `on${Capitalize<K>}`]: (event: EventMap[K]) => void;
};
Try it Yourself »
Utility Types
TypeScript provides several built-in utility types for common type transformations.
Common Utility Types
Use built-ins like Partial
, Pick
, and Omit
for common transformations.
// Basic types
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
// Make all properties optional
type PartialUser = Partial<User>;
// make all properties required
type RequiredUser = Required<PartialUser>;
// make all properties read-only
type ReadonlyUser = Readonly<User>;
// pick specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
// omit specific properties
type UserWithoutEmail = Omit<User, 'email'>;
// extract property types
type UserId = User['id']; // number
type UserKeys = keyof User; // 'id' | 'name' | 'email' | 'createdAt'
Try it Yourself »
Advanced Utility Types
Exclude or extract members from unions and create custom mapped helpers.
// Create a type that excludes null and undefined
type NonNullable<T> = T extends null | undefined ? never : T;
// Exclude types from a union
type Numbers = 1 | 2 | 3 | 'a' | 'b';
type JustNumbers = Exclude<Numbers, string>; // 1 | 2 | 3
// Extract types from a union
type JustStrings = Extract<Numbers, string>; // 'a' | 'b'
// Get the type that is not in the second type
type A = { a: string; b: number; c: boolean };
type B = { a: string; b: number };
type C = Omit<A, keyof B>; // { c: boolean }
// Create a type with all properties as mutable
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
Try it Yourself »
Recursive Types
Recursive types are useful for modeling tree-like data structures where a type can reference itself.
Basic Recursive Type
Model self-referential structures like trees and nested JSON.
// Simple binary tree
type BinaryTree<T> = {
value: T;
left?: BinaryTree<T>;
right?: BinaryTree<T>;
};
// JSON-like data structure
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// Nested comments
type Comment = {
id: number;
content: string;
replies: Comment[];
createdAt: Date;
};
Try it Yourself »
Advanced Recursive Types
Express linked lists, directory trees, and recursive state machines.
// Type for a linked list
type LinkedList<T> = {
value: T;
next: LinkedList<T> | null;
};
// Type for a directory structure
type File = {
type: 'file';
name: string;
size: number;
};
type Directory = {
type: 'directory';
name: string;
children: (File | Directory)[];
};
// Type for a state machine
type State = {
value: string;
transitions: {
[event: string]: State;
};
};
// Type for a recursive function
type RecursiveFunction<T> = (x: T | RecursiveFunction<T>) => void;
Try it Yourself »
Best Practices
When to Use Advanced Types
- Use mapped types when you need to transform multiple properties of an object type
- Use conditional types when your type depends on another type
- Use template literal types for string manipulation and pattern matching
- Use utility types for common transformations (prefer built-in ones when possible)
- Use recursive types for tree-like or nested data structures
Performance Considerations
- Deeply nested recursive types can slow down the TypeScript compiler
- Very large union types (100+ members) can cause performance issues
- Use type aliases to break down complex types
Common Pitfalls
Type Inference Issues
- Conditional types distribute over union types, which can be surprising
- Type inference with
infer
works differently in different contexts - Some utility types don't work well with
any
orunknown
Maintainability
- Overusing complex types can make code hard to understand
- Document complex type transformations with comments
- Consider using type assertions or helper functions for very complex types