TypeScript Expert
Overview
Advanced expertise in TypeScript — from the core type system and generics to advanced type manipulation, compiler configuration, declaration files, and real-world patterns for React, Node.js, and library authoring.
1. Type System Fundamentals
// Primitive types
let name: string = 'Alice'
let age: number = 30
let active: boolean = true
let nothing: null = null
let missing: undefined = undefined
let unique: symbol = Symbol('id')
let big: bigint = 9007199254740991n
// Arrays & Tuples
let ids: number[] = [1, 2, 3]
let pair: [string, number] = ['Alice', 30]
let rgb: readonly [number, number, number] = [255, 0, 128]
// Object types
type Point = { x: number; y: number }
type User = { readonly id: string; name: string; email?: string }
// Union & Intersection
type ID = string | number
type Admin = User & { role: 'admin'; permissions: string[] }
// Literal types
type Direction = 'north' | 'south' | 'east' | 'west'
type StatusCode = 200 | 201 | 400 | 401 | 404 | 500
// unknown vs any
function parse(input: unknown) {
if (typeof input === 'string') return input.toUpperCase() // narrowed
}
2. Interfaces vs Type Aliases
// Interface — extendable, declaration merging
interface Animal { name: string }
interface Dog extends Animal { breed: string }
// Declaration merging (interfaces only)
interface Window { myPlugin: Plugin } // augments existing Window
// Type alias — more powerful for complex types
type StringOrNumber = string | number
type Callback<T> = (error: Error | null, result: T) => void
type DeepReadonly<T> = { readonly [K in keyof T]: DeepReadonly<T[K]> }
// When to use which:
// - Use interface for objects/classes (extendable, mergeable)
// - Use type for unions, intersections, mapped types, utility compositions
3. Generics
// Generic functions
function identity<T>(value: T): T { return value }
function first<T>(arr: T[]): T | undefined { return arr[0] }
function merge<A, B>(a: A, b: B): A & B { return { ...a, ...b } as A & B }
// Constraints
function getLength<T extends { length: number }>(val: T): number { return val.length }
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key] }
// Generic interfaces & classes
interface Repository<T extends { id: string }> {
findById(id: string): Promise<T | null>
findAll(): Promise<T[]>
save(entity: T): Promise<T>
delete(id: string): Promise<void>
}
// Generic with default
interface ApiResponse<T = unknown> {
data: T
status: number
message: string
}
// Multiple constraints
function clone<T extends object>(obj: T): T {
return JSON.parse(JSON.stringify(obj))
}
4. Utility Types
// Built-in utility types
type Partial<T> // all props optional
type Required<T> // all props required
type Readonly<T> // all props readonly
type Record<K, V> // { [key in K]: V }
type Pick<T, K> // keep only K keys
type Omit<T, K> // remove K keys
type Exclude<T, U> // remove U from union T
type Extract<T, U> // keep only U from union T
type NonNullable<T> // remove null | undefined
type ReturnType<F> // return type of function F
type Parameters<F> // parameter types of function F
type InstanceType<C> // instance type of class C
type Awaited<T> // unwrap Promise<T> → T
// Examples
type UserPreview = Pick<User, 'id' | 'name'>
type UserUpdate = Partial<Omit<User, 'id'>>
type StringKeys<T> = Extract<keyof T, string>
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T
// Custom utilities
type Nullable<T> = T | null
type Maybe<T> = T | null | undefined
type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }
type Mutable<T> = { -readonly [K in keyof T]: T[K] }
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T]
5. Mapped Types
// Basic mapped type
type Flags<T> = { [K in keyof T]: boolean }
// Remapping keys with as
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
// Filtering keys
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K]
}
// Example
type UserGetters = Getters<{ name: string; age: number }>
// → { getName: () => string; getAge: () => number }
// Optional/required modifiers
type OptionalProps<T> = { [K in keyof T]+?: T[K] } // add optional
type RequiredProps<T> = { [K in keyof T]-?: T[K] } // remove optional
type MutableProps<T> = { -readonly [K in keyof T]: T[K] } // remove readonly
6. Conditional Types
// Basic conditional
type IsString<T> = T extends string ? true : false
// Distributive conditional (applies to each union member)
type ToArray<T> = T extends unknown ? T[] : never
// ToArray<string | number> → string[] | number[]
// infer — extract types
type UnpackArray<T> = T extends (infer Item)[] ? Item : T
type UnpackPromise<T> = T extends Promise<infer R> ? R : T
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never
// Recursive types
type DeepReadonly<T> = T extends (infer U)[]
? ReadonlyArray<DeepReadonly<U>>
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T
// NonNullable implementation
type MyNonNullable<T> = T extends null | undefined ? never : T
7. Template Literal Types
type EventName = 'click' | 'focus' | 'blur'
type Handler = `on${Capitalize<EventName>}`
// → 'onClick' | 'onFocus' | 'onBlur'
type CSSProperty = 'margin' | 'padding'
type CSSDirection = 'Top' | 'Bottom' | 'Left' | 'Right'
type CSSProp = `${CSSProperty}${CSSDirection}`
// → 'marginTop' | 'marginBottom' | ... | 'paddingRight'
// Parsing string patterns
type ExtractRoute<T extends string> =
T extends `${infer _Start}:${infer Param}/${infer Rest}`
? Param | ExtractRoute<`/${Rest}`>
: T extends `${infer _Start}:${infer Param}`
? Param
: never
type Params = ExtractRoute<'/users/:id/posts/:postId'>
// → 'id' | 'postId'
8. Type Narrowing & Type Guards
// typeof narrowing
function process(val: string | number) {
if (typeof val === 'string') return val.toUpperCase()
return val.toFixed(2)
}
// instanceof narrowing
function handleError(err: unknown) {
if (err instanceof Error) console.error(err.message)
}
// in narrowing
type Cat = { meow(): void }
type Dog = { bark(): void }
function speak(animal: Cat | Dog) {
if ('meow' in animal) animal.meow()
else animal.bark()
}
// Discriminated union
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rect'; width: number; height: number }
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2
case 'rect': return shape.width * shape.height
}
}
// User-defined type guard
function isUser(val: unknown): val is User {
return typeof val === 'object' && val !== null && 'id' in val && 'name' in val
}
// Assertion function
function assertDefined<T>(val: T | null | undefined): asserts val is T {
if (val == null) throw new Error('Expected defined value')
}
9. satisfies & as const
// as const — infer literal types
const config = {
endpoint: '/api/v1',
timeout: 3000,
methods: ['GET', 'POST'] as const,
} as const
// config.endpoint: '/api/v1' (literal, not string)
// config.methods: readonly ['GET', 'POST']
// satisfies — validate type without widening
const palette = {
red: [255, 0, 0],
green: '#00ff00',
} satisfies Record<string, string | number[]>
// palette.red is number[] (not string | number[])
// palette.green is string (not string | number[])
// Combine both
const routes = {
home: '/',
users: '/users',
userDetail: '/users/:id',
} as const satisfies Record<string, `/${string}`>
10. tsconfig.json — Key Options
{
"compilerOptions": {
// Strictness
"strict": true, // enables all strict checks
"noUncheckedIndexedAccess": true, // arr[0] → T | undefined
"exactOptionalPropertyTypes": true, // { x?: string } ≠ { x: string | undefined }
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
// Module
"target": "ES2022",
"module": "NodeNext", // or "ESNext" for bundlers
"moduleResolution": "NodeNext", // or "Bundler"
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// Output
"outDir": "./dist",
"rootDir": "./src",
"declaration": true, // generate .d.ts files
"declarationMap": true, // source maps for .d.ts
"sourceMap": true,
// Paths
"baseUrl": ".",
"paths": { "@/*": ["./src/*"] },
// Next.js / Bundler
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }]
}
}
11. Declaration Files & Module Augmentation
// Augment existing module
declare module 'express' {
interface Request {
user?: User
requestId: string
}
}
// Augment global
declare global {
interface Window {
analytics: Analytics
}
var __APP_VERSION__: string
}
// .d.ts for untyped JS modules
declare module 'untyped-lib' {
export function doSomething(val: string): number
export const VERSION: string
}
// Ambient declarations
declare const __DEV__: boolean
declare function require(module: string): unknown
12. Advanced Patterns
// Builder pattern with method chaining
class QueryBuilder<T> {
private filters: Partial<T>[] = []
where(filter: Partial<T>): this { this.filters.push(filter); return this }
build(): Partial<T> { return Object.assign({}, ...this.filters) }
}
// Branded types (nominal typing)
type UserId = string & { readonly brand: unique symbol }
type PostId = string & { readonly brand: unique symbol }
const toUserId = (id: string): UserId => id as UserId
// UserId and PostId are incompatible even though both are strings
// Const enum (inlined at compile time)
const enum Direction { Up = 'UP', Down = 'DOWN' }
// Variadic tuple types
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
type Prepend<T, U extends unknown[]> = [T, ...U]
// Function overloads
function format(value: string): string
function format(value: number, decimals: number): string
function format(value: string | number, decimals = 2): string {
if (typeof value === 'string') return value.trim()
return value.toFixed(decimals)
}
Core Competency Summary
- Write fully type-safe TypeScript with
strictmode enabled - Master generics, constraints, and generic utility composition
- Use mapped types, conditional types, and template literals for advanced type manipulation
- Narrow types with type guards, discriminated unions, and assertion functions
- Configure
tsconfig.jsonfor Node.js, Next.js, and library projects - Author
.d.tsdeclaration files and module augmentations - Apply branded types, builder patterns, and overloads in real-world code