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.
Scanned 5/28/2026
Install via CLI
openskills install andykenward/github-actions-cloudflare-pages---
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:**
```typescript
function identity<T>(value: T): T {
return value
}
const num = identity<number>(42) // Type: number
const str = identity<string>('hello') // Type: string
const auto = identity(true) // Type inferred: boolean
```
**Generic Constraints:**
```typescript
interface HasLength {
length: number
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length)
return item
}
logLength('hello') // OK: string has length
logLength([1, 2, 3]) // OK: array has length
logLength({length: 10}) // OK: object has length
// logLength(42); // Error: number has no length
```
**Multiple Type Parameters:**
```typescript
function merge<T, U>(obj1: T, obj2: U): T & U {
return {...obj1, ...obj2}
}
const merged = merge({name: 'John'}, {age: 30})
// Type: { name: string } & { age: number }
```
### 2. Conditional Types
**Purpose:** Create types that depend on conditions, enabling sophisticated type logic.
**Basic Conditional Type:**
```typescript
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
```
**Extracting Return Types:**
```typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
function getUser() {
return {id: 1, name: 'John'}
}
type User = ReturnType<typeof getUser>
// Type: { id: number; name: string; }
```
**Distributive Conditional Types:**
```typescript
type ToArray<T> = T extends any ? T[] : never
type StrOrNumArray = ToArray<string | number>
// Type: string[] | number[]
```
**Nested Conditions:**
```typescript
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> // "string"
type T2 = TypeName<() => void> // "function"
```
### 3. Mapped Types
**Purpose:** Transform existing types by iterating over their properties.
**Basic Mapped Type:**
```typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
interface User {
id: number
name: string
}
type ReadonlyUser = Readonly<User>
// Type: { readonly id: number; readonly name: string; }
```
**Optional Properties:**
```typescript
type Partial<T> = {
[P in keyof T]?: T[P]
}
type PartialUser = Partial<User>
// Type: { id?: number; name?: string; }
```
**Key Remapping:**
```typescript
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface Person {
name: string
age: number
}
type PersonGetters = Getters<Person>
// Type: { getName: () => string; getAge: () => number; }
```
**Filtering Properties:**
```typescript
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>
// Type: { id: number; age: number; }
```
### 4. Template Literal Types
**Purpose:** Create string-based types with pattern matching and transformation.
**Basic Template Literal:**
```typescript
type EventName = 'click' | 'focus' | 'blur'
type EventHandler = `on${Capitalize<EventName>}`
// Type: "onClick" | "onFocus" | "onBlur"
```
**String Manipulation:**
```typescript
type UppercaseGreeting = Uppercase<'hello'> // "HELLO"
type LowercaseGreeting = Lowercase<'HELLO'> // "hello"
type CapitalizedName = Capitalize<'john'> // "John"
type UncapitalizedName = Uncapitalize<'John'> // "john"
```
**Path Building:**
```typescript
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>
// Type: "server" | "database" | "server.host" | "server.port" | "database.url"
```
### 5. Utility Types
**Built-in Utility Types:**
```typescript
// Partial<T> - Make all properties optional
type PartialUser = Partial<User>
// Required<T> - Make all properties required
type RequiredUser = Required<PartialUser>
// Readonly<T> - Make all properties readonly
type ReadonlyUser = Readonly<User>
// Pick<T, K> - Select specific properties
type UserName = Pick<User, 'name' | 'email'>
// Omit<T, K> - Remove specific properties
type UserWithoutPassword = Omit<User, 'password'>
// Exclude<T, U> - Exclude types from union
type T1 = Exclude<'a' | 'b' | 'c', 'a'> // "b" | "c"
// Extract<T, U> - Extract types from union
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'b'> // "a" | "b"
// NonNullable<T> - Exclude null and undefined
type T3 = NonNullable<string | null | undefined> // string
// Record<K, T> - Create object type with keys K and values T
type PageInfo = Record<'home' | 'about', {title: string}>
```
## Advanced Patterns
### Pattern 1: Type-Safe Event Emitter
```typescript
type EventMap = {
'user:created': {id: string; name: string}
'user:updated': {id: string}
'user:deleted': {id: string}
}
class TypedEventEmitter<T extends Record<string, any>> {
private listeners: {
[K in keyof T]?: Array<(data: T[K]) => void>
} = {}
on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = []
}
this.listeners[event]!.push(callback)
}
emit<K extends keyof T>(event: K, data: T[K]): void {
const callbacks = this.listeners[event]
if (callbacks) {
callbacks.forEach(callback => callback(data))
}
}
}
const emitter = new TypedEventEmitter<EventMap>()
emitter.on('user:created', data => {
console.log(data.id, data.name) // Type-safe!
})
emitter.emit('user:created', {id: '1', name: 'John'})
// emitter.emit("user:created", { id: "1" }); // Error: missing 'name'
```
### Pattern 2: Type-Safe API Client
```typescript
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type EndpointConfig = {
'/users': {
GET: {response: User[]}
POST: {body: {name: string; email: string}; response: User}
}
'/users/:id': {
GET: {params: {id: string}; response: User}
PUT: {params: {id: string}; body: Partial<User>; response: User}
DELETE: {params: {id: string}; response: void}
}
}
type ExtractParams<T> = T extends {params: infer P} ? P : never
type ExtractBody<T> = T extends {body: infer B} ? B : never
type ExtractResponse<T> = T extends {response: infer R} ? R : never
class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> {
async request<Path extends keyof Config, Method extends keyof Config[Path]>(
path: Path,
method: Method,
...[options]: ExtractParams<Config[Path][Method]> extends never
? ExtractBody<Config[Path][Method]> extends never
? []
: [{body: ExtractBody<Config[Path][Method]>}]
: [
{
params: ExtractParams<Config[Path][Method]>
body?: ExtractBody<Config[Path][Method]>
}
]
): Promise<ExtractResponse<Config[Path][Method]>> {
// Implementation here
return {} as any
}
}
const api = new APIClient<EndpointConfig>()
// Type-safe API calls
const users = await api.request('/users', 'GET')
// Type: User[]
const newUser = await api.request('/users', 'POST', {
body: {name: 'John', email: 'john@example.com'}
})
// Type: User
const user = await api.request('/users/:id', 'GET', {
params: {id: '123'}
})
// Type: User
```
### Pattern 3: Builder Pattern with Type Safety
```typescript
type BuilderState<T> = {
[K in keyof T]: T[K] | undefined
}
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K
}[keyof T]
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never
}[keyof T]
type IsComplete<T, S> =
RequiredKeys<T> extends keyof S
? S[RequiredKeys<T>] extends undefined
? false
: true
: false
class Builder<T, S extends BuilderState<T> = {}> {
private state: S = {} as S
set<K extends keyof T>(key: K, value: T[K]): Builder<T, S & Record<K, T[K]>> {
this.state[key] = value
return this as any
}
build(this: IsComplete<T, S> extends true ? this : never): T {
return this.state as T
}
}
interface User {
id: string
name: string
email: string
age?: number
}
const builder = new Builder<User>()
const user = builder
.set('id', '1')
.set('name', 'John')
.set('email', 'john@example.com')
.build() // OK: all required fields set
// const incomplete = builder
// .set("id", "1")
// .build(); // Error: missing required fields
```
### Pattern 4: Deep Readonly/Partial
```typescript
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepReadonly<T[P]>
: T[P]
}
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: DeepPartial<T[P]>
: T[P]
}
interface Config {
server: {
host: string
port: number
ssl: {
enabled: boolean
cert: string
}
}
database: {
url: string
pool: {
min: number
max: number
}
}
}
type ReadonlyConfig = DeepReadonly<Config>
// All nested properties are readonly
type PartialConfig = DeepPartial<Config>
// All nested properties are optional
```
### Pattern 5: Type-Safe Form Validation
```typescript
type ValidationRule<T> = {
validate: (value: T) => boolean
message: string
}
type FieldValidation<T> = {
[K in keyof T]?: ValidationRule<T[K]>[]
}
type ValidationErrors<T> = {
[K in keyof T]?: string[]
}
class FormValidator<T extends Record<string, any>> {
constructor(private rules: FieldValidation<T>) {}
validate(data: T): ValidationErrors<T> | null {
const errors: ValidationErrors<T> = {}
let hasErrors = false
for (const key in this.rules) {
const fieldRules = this.rules[key]
const value = data[key]
if (fieldRules) {
const fieldErrors: string[] = []
for (const rule of fieldRules) {
if (!rule.validate(value)) {
fieldErrors.push(rule.message)
}
}
if (fieldErrors.length > 0) {
errors[key] = fieldErrors
hasErrors = true
}
}
}
return hasErrors ? errors : null
}
}
interface LoginForm {
email: string
password: string
}
const validator = new FormValidator<LoginForm>({
email: [
{
validate: v => v.includes('@'),
message: 'Email must contain @'
},
{
validate: v => v.length > 0,
message: 'Email is required'
}
],
password: [
{
validate: v => v.length >= 8,
message: 'Password must be at least 8 characters'
}
]
})
const errors = validator.validate({
email: 'invalid',
password: 'short'
})
// Type: { email?: string[]; password?: string[]; } | null
```
### Pattern 6: Discriminated Unions
```typescript
type Success<T> = {
status: 'success'
data: T
}
type Error = {
status: 'error'
error: string
}
type Loading = {
status: 'loading'
}
type AsyncState<T> = Success<T> | Error | Loading
function handleState<T>(state: AsyncState<T>): void {
switch (state.status) {
case 'success':
console.log(state.data) // Type: T
break
case 'error':
console.log(state.error) // Type: string
break
case 'loading':
console.log('Loading...')
break
}
}
// Type-safe state machine
type State =
| {type: 'idle'}
| {type: 'fetching'; requestId: string}
| {type: 'success'; data: any}
| {type: 'error'; error: Error}
type Event =
| {type: 'FETCH'; requestId: string}
| {type: 'SUCCESS'; data: any}
| {type: 'ERROR'; error: Error}
| {type: 'RESET'}
function reducer(state: State, event: Event): State {
switch (state.type) {
case 'idle':
return event.type === 'FETCH'
? {type: 'fetching', requestId: event.requestId}
: state
case 'fetching':
if (event.type === 'SUCCESS') {
return {type: 'success', data: event.data}
}
if (event.type === 'ERROR') {
return {type: 'error', error: event.error}
}
return state
case 'success':
case 'error':
return event.type === 'RESET' ? {type: 'idle'} : state
}
}
```
## Type Inference Techniques
### 1. Infer Keyword
```typescript
// Extract array element type
type ElementType<T> = T extends (infer U)[] ? U : never
type NumArray = number[]
type Num = ElementType<NumArray> // number
// Extract promise type
type PromiseType<T> = T extends Promise<infer U> ? U : never
type AsyncNum = PromiseType<Promise<number>> // number
// Extract function parameters
type Parameters<T> = T extends (...args: infer P) => any ? P : never
function foo(a: string, b: number) {}
type FooParams = Parameters<typeof foo> // [string, number]
```
### 2. Type Guards
```typescript
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function isArrayOf<T>(
value: unknown,
guard: (item: unknown) => item is T
): value is T[] {
return Array.isArray(value) && value.every(guard)
}
const data: unknown = ['a', 'b', 'c']
if (isArrayOf(data, isString)) {
data.forEach(s => s.toUpperCase()) // Type: string[]
}
```
### 3. Assertion Functions
```typescript
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Not a string')
}
}
function processValue(value: unknown) {
assertIsString(value)
// value is now typed as string
console.log(value.toUpperCase())
}
```
## Best Practices
1. **Use `unknown` over `any`**: Enforce type checking
2. **Prefer `interface` for object shapes**: Better error messages
3. **Use `type` for unions and complex types**: More flexible
4. **Leverage type inference**: Let TypeScript infer when possible
5. **Create helper types**: Build reusable type utilities
6. **Use const assertions**: Preserve literal types
7. **Avoid type assertions**: Use type guards instead
8. **Document complex types**: Add JSDoc comments
9. **Use strict mode**: Enable all strict compiler options
10. **Test your types**: Use type tests to verify type behavior
## Type Testing
```typescript
// Type assertion tests
type AssertEqual<T, U> = [T] extends [U]
? [U] extends [T]
? true
: false
: false
type Test1 = AssertEqual<string, string> // true
type Test2 = AssertEqual<string, number> // false
type Test3 = AssertEqual<string | number, string> // false
// Expect error helper
type ExpectError<T extends never> = T
// Example usage
type ShouldError = ExpectError<AssertEqual<string, number>>
```
## Common Pitfalls
1. **Over-using `any`**: Defeats the purpose of TypeScript
2. **Ignoring strict null checks**: Can lead to runtime errors
3. **Too complex types**: Can slow down compilation
4. **Not using discriminated unions**: Misses type narrowing opportunities
5. **Forgetting readonly modifiers**: Allows unintended mutations
6. **Circular type references**: Can cause compiler errors
7. **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
No comments yet. Be the first to comment!