| name | typescript-advanced |
| description | Advanced TypeScript: generics, conditional types, mapped types, template literals, utility types, type guards, discriminated unions at scale |
TypeScript Advanced Skill
When to activate
- Designing a type-safe API or SDK where generics are essential
- Writing utility types to eliminate type duplication
- Narrowing types with discriminated unions and type guards
- Getting TypeScript to infer types rather than annotating manually
- Debugging complex type errors in generic code
- Building a type-safe event system, state machine, or plugin architecture
When NOT to use
- Basic TypeScript with simple types — the TypeScript rules skill covers that
- When
any is genuinely the right answer (rare, but it happens at external API boundaries)
- Over-engineering types for simple internal code — readability > type cleverness
Instructions
Generics — make types work for you
function first<T>(arr: T[]): T | undefined {
return arr[0]
}
const n = first([1, 2, 3])
const s = first(['a', 'b'])
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { id: 1, name: 'Alice' }
const name = getProperty(user, 'name')
type ApiResponse<T = unknown> = {
data: T
status: number
message: string
}
Discriminated unions — the foundation of safe state
type LoadingState = { status: 'loading' }
type SuccessState<T> = { status: 'success'; data: T }
type ErrorState = { status: 'error'; error: Error; retryable: boolean }
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState
function renderState<T>(state: AsyncState<T>) {
switch (state.status) {
case 'loading': return <Spinner />
case 'success': return <Data data={state.data} />
case 'error': return <Error error={state.error} retryable={state.retryable} />
default: {
const : = state
()
}
}
}
Type guards — runtime narrowing with type safety
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'email' in value &&
typeof (value as User).email === 'string'
)
}
function assertUser(value: unknown): asserts value is User {
if (!isUser(value)) throw new TypeError(`Expected User, got ${typeof value}`)
}
const data = await fetchUser()
if (isUser(data)) {
console.log(data.email)
}
assertUser(data)
console.log(data.email)
Utility types — avoid repeating yourself
type User = {
id: number
email: string
name: string
passwordHash: string
createdAt: Date
}
type PublicUser = Omit<User, 'passwordHash'>
type UpdateUserRequest = Partial<Pick<User, 'email' | 'name'>>
type CreateUserRequest = Required<Pick<User, 'email' | 'name'>>
type ReadonlyUser = Readonly<User>
async function fetchUser(id: number) {
return db.user.findUnique({ where: { id } })
}
type FetchedUser = Awaited<< fetchUser>>
= < fetchUser>[]
Conditional types
type IsArray<T> = T extends unknown[] ? true : false
type A = IsArray<string[]>
type B = IsArray<string>
type ElementType<T> = T extends (infer E)[] ? E : T
type C = ElementType<string[]>
type D = ElementType<string>
type StrictNonNullable<T> = T extends null | undefined ? never : T
type E = StrictNonNullable<string | null | undefined>
type HasId<T> = T extends { id: unknown } ? true : false
Mapped types — transform object shapes
type Nullable<T> = { [K in keyof T]: T[K] | null }
type Asyncify<T> = { [K in keyof T]: T[K] extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: T[K]
}
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K]
}
type OnlyStrings = PickByValue<{ a: string; b: number; c: string }, string>
type Status = 'pending' | 'active' | 'inactive'
type StatusConfig = Record<Status, { label: string; color: string }>
Template literal types
type Size = 'sm' | 'md' | 'lg'
type Color = 'primary' | 'secondary'
type ButtonVariant = `${Color}-${Size}`
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<`/${Rest}`>
: T extends `${string}:${infer Param}`
? Param
: never
type Params = ExtractRouteParams<'/users/:userId/posts/:postId'>
type EventMap = {
'user:created': { userId: string; email: string }
'user:deleted': { : }
: { : ; : }
}
= keyof
emit<E >(: E, : [E]): {
}
(, { : , : })
(, { : })
Infer — extract types from other types
type Awaited<T> = T extends Promise<infer U> ? U : T
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never
type ConstructorParams<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never
type ArrayElement<T extends readonly unknown[]> =
T extends readonly (infer E)[] ? E : never
type Nums = ArrayElement<number[]>
satisfies operator (TS 4.9+)
type Config = {
port: number
host: string
features: Record<string, boolean>
}
const config = {
port: 3000,
host: 'localhost',
features: { dark_mode: true, beta: false },
} satisfies Config
config.port.toFixed(0)
config.features.dark_mode
config.features.nonexistent
Example
User: Build a type-safe API client where the request and response types are inferred from a route definition object, with no manual type annotations at call sites.
Expected output:
type Routes = {
'GET /users': { response: User[] }
'GET /users/:id': { params: { id: string }; response: User }
'POST /users': { body: CreateUserRequest; response: User }
}