Advanced TypeScript
Advanced TypeScript patterns for agents — generic types, conditional types, mapped types, discriminated unions, type guards, utility types, declaration files, strict mode, and module resolution.
Advanced TypeScript
You are an autonomous agent that writes and maintains TypeScript code. Your role is to leverage the type system to catch bugs at compile time, produce self-documenting code, and build APIs that guide consumers toward correct usage through types alone.
Philosophy
TypeScript's type system is a tool for expressing intent. The goal is not to make the compiler happy — it is to encode domain rules, prevent invalid states, and make incorrect code impossible to write. Invest in precise types upfront; they pay dividends in fewer runtime errors and better developer experience.
Techniques
Generic Types
- Use generics when a function or type works with multiple types while preserving relationships:
function first<T>(arr: T[]): T | undefined. - Constrain generics with
extendsto narrow the accepted types:function getLength<T extends { length: number }>(item: T): number. - Use default type parameters for common cases:
type Response<T = unknown> = { data: T; status: number }. - Name generic parameters meaningfully for complex generics:
TInput,TOutput,TKeyinstead of single letters when clarity demands it.
Conditional Types
- Use conditional types for type-level branching:
type IsString<T> = T extends string ? true : false. - Use
inferto extract types within conditional expressions:type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never. - Conditional types distribute over unions:
IsString<string | number>becomestrue | false. - Use conditional types in library and utility code. Avoid them in application-level types where simpler constructs work.
Mapped Types
- Transform existing types with mapped types:
type Readonly<T> = { readonly [K in keyof T]: T[K] }. - Use key remapping with
as:type Getters<T> = { [K in keyof T asget${Capitalize<K & string>}]: () => T[K] }. - Combine with conditional types for selective transformation: make only string properties optional, for example.
- Understand built-in mapped types before building custom ones.
Template Literal Types
- Use template literal types for string pattern enforcement:
type EventName =on${Capitalize<string>}``. - Combine with mapped types for type-safe event systems or API route definitions.
- Use for CSS unit types:
type CSSLength =${number}${'px' | 'rem' | 'em'}``. - Keep template literal types focused. Overly complex string type patterns become unreadable.
Discriminated Unions
- Model states with discriminated unions using a shared literal type field:
type Result<T> = { status: 'success'; data: T } | { status: 'error'; error: Error } - Use
switchorifon the discriminant field for exhaustive handling. - Add a
neverdefault case to catch unhandled variants at compile time. - Prefer discriminated unions over boolean flags.
{ loading: boolean; error: boolean; data: T | null }has impossible states; a union does not.
Type Guards
- Write custom type guards with
isreturn type:function isUser(val: unknown): val is User. - Use
typeoffor primitive narrowing andinoperator for property checks. - Use
satisfiesoperator to validate a value matches a type without widening:const config = { ... } satisfies Config. - Validate external data (API responses, user input) at runtime boundaries with type guards or validation libraries like Zod.
Utility Types
Pick<T, K>: Select specific properties from a type.Omit<T, K>: Remove specific properties from a type.Partial<T>: Make all properties optional. Useful for update operations.Required<T>: Make all properties required. Useful for ensuring complete configuration.Record<K, V>: Create an object type with specified keys and value types.Extract<T, U>andExclude<T, U>: Filter union types.NonNullable<T>: Removenullandundefinedfrom a type.- Compose utility types:
Partial<Pick<User, 'name' | 'email'>>for targeted partial updates.
Declaration Files
- Write
.d.tsfiles to type untyped JavaScript libraries or global declarations. - Use
declare modulefor augmenting existing module types. - Use
declare globalto extend global types (Window, NodeJS.ProcessEnv). - Check DefinitelyTyped (
@types/*) before writing custom declarations.
Strict Mode
- Enable all strict flags in
tsconfig.json:"strict": true. - Key strict options:
strictNullChecks(no implicit null),noImplicitAny(no untyped variables),strictFunctionTypes(correct function variance). - Enable
noUncheckedIndexedAccessto treat index access as potentially undefined. - Strict mode catches real bugs. Never disable it to "make things easier."
Module Resolution
- Use
"moduleResolution": "bundler"for modern bundler-based projects or"nodenext"for Node.js packages. - Configure path aliases in
tsconfig.jsonwithpathsand ensure the bundler resolves them too. - Use
import typefor type-only imports to ensure they are erased at compile time. - Understand the difference between
require(CommonJS) andimport(ESM). Configure"module"appropriately.
Best Practices
- Start with strict mode on every new project. Retrofitting strict mode onto a large codebase is painful.
- Use
unknowninstead ofanyfor values of uncertain type.unknownforces safe narrowing. - Use
as constfor literal type inference on objects and arrays. - Export types alongside their implementations. Consumers should not need to reconstruct your types.
- Use Zod, Valibot, or ArkType for runtime validation that generates TypeScript types.
Anti-Patterns
- Using
anyto silence type errors instead of fixing the underlying type issue. - Casting with
aswhen a type guard or proper narrowing would be safer. - Disabling strict mode or individual strict checks to avoid fixing type errors.
- Creating overly complex conditional types that no one on the team can read or maintain.
- Using
@ts-ignoreor@ts-expect-errorwithout a comment explaining why. - Defining all types as
interfacewhentypewould be more appropriate (unions, intersections, primitives). - Not using discriminated unions for state management, leading to impossible state combinations.
- Ignoring the
satisfiesoperator and usingasfor type validation instead.
Related Skills
Abstraction Control
Avoiding over-abstraction and unnecessary complexity by choosing the simplest solution that solves the actual problem
Accessibility Implementation
Making web content accessible through ARIA attributes, semantic HTML, keyboard navigation, screen reader support, color contrast, focus management, and WCAG compliance.
API Design Patterns
Designing and implementing clean APIs with proper REST conventions, pagination, versioning, authentication, and backward compatibility.
API Integration
Integrating with external APIs effectively — reading API docs, authentication patterns, error handling, rate limiting, retry with backoff, response validation, SDK vs raw HTTP decisions, and API versioning.
Assumption Validation
Detecting and validating assumptions before acting on them to prevent cascading errors from wrong guesses
Authentication Implementation
Implementing authentication flows correctly including OAuth 2.0/OIDC, JWT handling, session management, password hashing, MFA, token refresh, and CSRF protection.