| name | typescript |
| description | Write clean, type-safe TypeScript code using modern patterns, strict configuration, and best practices. Use when writing TypeScript code, configuring projects, or solving type-related challenges. |
TypeScript Expert
Write clean, type-safe TypeScript code that leverages the full power of the type system to catch bugs at compile time.
When to Use This Skill
Use this skill when:
- Writing or refactoring TypeScript code
- Configuring TypeScript projects (tsconfig.json)
- Solving complex type-related challenges
- Choosing between type system patterns
- Validating external data with types
Core Workflow
1. Type Decision Tree
Choose the right construct:
| Use Case | Use | Not |
|---|---|---|
| Object shapes | interface |
type |
| Unions/primitives | type |
interface |
| Dynamic data | unknown |
any |
| State machines | Discriminated unions | Complex conditionals |
| Domain types | Branded types | Plain primitives |
Example:
// ✅ Correct choices
interface User { id: number; name: string } // Object shape
type Status = 'idle' | 'loading' | 'success' // Union
type USD = number & { readonly __brand: 'USD' } // Branded type
// ❌ Wrong choices
type User = { id: number } // Use interface
interface Status { /* ... */ } // Can't do unions
2. State Modeling Pattern
For finite states, always use discriminated unions to eliminate impossible states:
type ApiState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string }
| { status: 'error'; message: string };
// Exhaustiveness checking
function handle(state: ApiState) {
switch (state.status) {
case 'success': return state.data;
case 'error': return state.message;
case 'idle': return 'Not started';
case 'loading': return 'Loading...';
default:
const _exhaustive: never = state; // Compiler error if cases missing
throw new Error('Unhandled state');
}
}
3. Validation Workflow
Always validate external data with runtime checks:
- Simple validation: Type guards
- Complex validation: Schema libraries (Zod, Yup)
// Type guard pattern
function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
typeof (data as any).id === 'number'
);
}
// Schema validation (preferred for APIs)
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
const user = UserSchema.parse(apiData); // Runtime validation + types
Never use type assertions without validation:
// ❌ BAD: No runtime check
const user = data as User;
// ✅ GOOD: Validated first
if (isUser(data)) {
// data is User here
}
4. Configuration Checklist
For new projects, enable strict mode plus:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"moduleResolution": "bundler"
}
}
Why these matter:
noUncheckedIndexedAccess- Prevents undefined access bugsexactOptionalPropertyTypes- Distinguishes missing from undefinedmoduleResolution: "bundler"- Optimized for Vite/esbuild
5. Code Organization Rules
Barrel files: Avoid for internal code (75% faster builds)
// ❌ BAD: Internal barrel
// src/components/index.ts
export * from './Button';
// ✅ GOOD: Direct imports + path aliases
// tsconfig.json: "paths": { "@/components/*": ["src/components/*"] }
import { Button } from '@/components/Button';
Only use barrel files for:
- Library entry points
- Public APIs
- Small modules (<10 exports)
Quick Reference Patterns
Utility Types
type UserPreview = Pick<User, 'id' | 'name'>; // Extract subset
type PublicUser = Omit<User, 'email'>; // Remove fields
type UpdateDto = Partial<User>; // Make optional
type CompleteUser = Required<User>; // Make required
type ImmutableUser = Readonly<User>; // Make readonly
type UserType = ReturnType<typeof getUser>; // Extract return type
Error Handling
// Catch with unknown
try { } catch (err: unknown) {
if (err instanceof Error) { /* ... */ }
}
// Result types for expected failures
type Result<T, E = Error> =
| { success: true; value: T }
| { success: false; error: E };
Readonly Patterns
const config: Readonly<Config> = { /* ... */ };
const numbers: readonly number[] = [1, 2, 3];
const ROUTES = { home: '/' } as const; // Deep readonly + literal types
When to Consult Detailed References
For comprehensive information on advanced patterns, configuration options, or specific features, read:
references/best-practices-2025.md- Full TypeScript best practices guide
The reference includes:
- Advanced type patterns (conditional types, mapped types, branded types)
- Complete tsconfig.json options
- Modern features (decorators, const type parameters)
- Common anti-patterns and solutions
Quality Checklist
Before completing TypeScript code:
- External data validated (not just typed)
- No
anytypes (or explicitly justified) - State machines use discriminated unions
- Utility types used where applicable
- Readonly applied to prevent mutations
- Exhaustiveness checks with
never