| name | typescript-advanced-types |
| description | Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects. |
TypeScript Advanced Types
Comprehensive guidance for mastering TypeScript's advanced type system including generics, conditional types, mapped types, template literal types, and utility types for building robust, type-safe applications.
When to Use This Skill
- Building type-safe libraries or frameworks
- Creating reusable generic components
- Implementing complex type inference logic
- Designing type-safe API clients
- Building form validation systems
- Creating strongly-typed configuration objects
- Implementing type-safe state management
- Migrating JavaScript codebases to TypeScript
Core Concepts
1. Generics
Purpose: Create reusable, type-flexible components while maintaining type safety.
Basic Generic Function:
function identity<T>(value: T): T {
return value
}
const num = identity<number>(42)
const str = identity<string>('hello')
const auto = identity(true)
Generic Constraints:
interface HasLength {
length: number
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length)
return item
}
logLength('hello')
logLength([1, 2, 3])
logLength({ length: 10 })
Multiple Type Parameters:
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 }
}
const merged = merge({ name: 'John' }, { age: 30 })
2. Conditional Types
Purpose: Create types that depend on conditions, enabling sophisticated type logic.
Basic Conditional Type:
type IsString<T> = T extends string ? true : false
type A = IsString<string>
type B = IsString<number>
Extracting Return Types:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
function getUser() {
return { id: 1, name: 'John' }
}
type User = ReturnType<typeof getUser>
Distributive Conditional Types:
type ToArray<T> = T extends any ? T[] : never
type StrOrNumArray = ToArray<string | number>
Nested Conditions:
type TypeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object'
type T1 = TypeName<string>
type T2 = TypeName<() => void>
3. Mapped Types
Purpose: Transform existing types by iterating over their properties.
Basic Mapped Type:
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
interface User {
id: number
name: string
}
type ReadonlyUser = Readonly<User>
Optional Properties:
type Partial<T> = {
[P in keyof T]?: T[P]
}
type PartialUser = Partial<User>
Key Remapping:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface Person {
name: string
age: number
}
type PersonGetters = Getters<Person>
Filtering Properties:
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
interface Mixed {
id: number
name: string
age: number
active: boolean
}
type OnlyNumbers = PickByType<Mixed, number>
4. Template Literal Types
Purpose: Create string-based types with pattern matching and transformation.
Basic Template Literal:
type EventName = 'click' | 'focus' | 'blur'
type EventHandler = `on${Capitalize<EventName>}`
String Manipulation:
type UppercaseGreeting = Uppercase<'hello'>
type LowercaseGreeting = Lowercase<'HELLO'>
type CapitalizedName = Capitalize<'john'>
type UncapitalizedName = Uncapitalize<'John'>
Path Building:
type Path<T> = T extends object
? {
[K in keyof T]: K extends string ? `${K}` | `${K}.${Path<T[K]>}` : never
}[keyof T]
: never
interface Config {
server: {
host: string
port: number
}
database: {
url: string
}
}
type ConfigPath = Path<Config>
5. Utility Types
Built-in Utility Types:
type PartialUser = Partial<User>
type RequiredUser = Required<PartialUser>
type ReadonlyUser = Readonly<User>
type UserName = Pick<User, 'name' | 'email'>
type UserWithoutPassword = Omit<User, 'password'>
type T1 = Exclude<'a' | 'b' | 'c', 'a'>
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'b'>
type T3 = NonNullable<string | null | undefined>
type PageInfo = Record<'home' | 'about', { title: string }>
Detailed worked examples and patterns
Detailed sections (starting with ## Advanced Patterns) live in references/details.md. Read that file when the navigation summary above is insufficient.
Best Practices
- Use
unknown over any: Enforce type checking
- Prefer
interface for object shapes: Better error messages
- Use
type for unions and complex types: More flexible
- Leverage type inference: Let TypeScript infer when possible
- Create helper types: Build reusable type utilities
- Use const assertions: Preserve literal types
- Avoid type assertions: Use type guards instead
- Document complex types: Add JSDoc comments
- Use strict mode: Enable all strict compiler options
- Test your types: Use type tests to verify type behavior
Type Testing
type AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : false) : false
type Test1 = AssertEqual<string, string>
type Test2 = AssertEqual<string, number>
type Test3 = AssertEqual<string | number, string>
type ExpectError<T extends never> = T
type ShouldError = ExpectError<AssertEqual<string, number>>
Common Pitfalls
- Over-using
any: Defeats the purpose of TypeScript
- Ignoring strict null checks: Can lead to runtime errors
- Too complex types: Can slow down compilation
- Not using discriminated unions: Misses type narrowing opportunities
- Forgetting readonly modifiers: Allows unintended mutations
- Circular type references: Can cause compiler errors
- Not handling edge cases: Like empty arrays or null values
Performance Considerations
- Avoid deeply nested conditional types
- Use simple types when possible
- Cache complex type computations
- Limit recursion depth in recursive types
- Use build tools to skip type checking in production