TypeScript Best Practices
Guidelines for writing clean, type-safe, and maintainable TypeScript code.
Note: If the repository has established code style conventions, follow those first. These guidelines serve as defaults.
Core Principles
- Type-First Design - Define types before implementation; minimize reliance on inference
- Interface for Structure - Use
interface for objects, type for unions/mapped/conditional
- Namespace for Type Organization - Group related types with namespaces (types only, not runtime)
- Generic Const for Strictness - Use
<const TConfig> for strict literal inference
- Extract, Don't Redefine - Get types from existing definitions instead of duplicating
- Strictest Config - Use strictest tsconfig base; install
ts-reset for saner built-in types
Quick Reference
interface vs type
| Use |
When |
interface |
Object structures, class contracts, extensible APIs |
type |
Union types, mapped types, conditional types, tuples |
Naming Conventions
| Element |
Convention |
Example |
| Interface/Type |
PascalCase |
UserProfile, ResponseData |
| Generic parameters |
T prefix |
TUser, TConfig (never bare T, K, V) |
| Acronyms |
First cap only |
userId, ApiResponse (NOT userID, APIResponse) |
| Constants |
UPPER_SNAKE |
MAX_RETRY_COUNT |
| Variables/Functions |
camelCase |
getUserById, isActive |
Array Syntax
| DO |
DON'T |
Array<TItem> |
TItem[] |
ReadonlyArray<TItem> |
readonly TItem[] |
Object Types
| Use Case |
DO |
DON'T |
| Empty object |
Record<string, never> |
{} |
| Any object (extends) |
Record<string, any> |
Record<string, unknown> |
| Any object (annotation) |
Record<string, unknown> |
Record<string, any> |
| Non-primitive |
object |
{} |
Assertions
| DO |
DON'T |
| Zod/arktype for runtime validation |
response as User |
satisfies for compile-time checks |
value as unknown as Type |
Type guards (if ('prop' in obj)) |
as any to silence errors |
| Explicit null checks |
x! non-null assertion |
Function Declarations
// DO: Type on the const
const myFunction: myFunction.Type = (options) => {
// implementation
};
// DO: satisfies when namespace doesn't exist
const => {
// implementation
}) satisfies React.ComponentProps<'button'>['onClick'];
Type Extraction
// DO: Extract from existing definitions
type
type ItemIds = Array<Item['id']>;
type TimeoutType = NonNullable<typeof config['timeout']>;
// DON'T: Manually redefine types
type BadItemIds = Array<number>; // Won't update if Item.id changes
Summary Checklist
Before committing TypeScript code, verify:
Reference Files
For detailed patterns and examples, see:
- type-patterns.md - Type syntax, assertions, namespace pattern, generics
- code-style.md - Safe array access, early return, avoid destructuring, avoid enum
- union-exhaustive.md - Discriminated unions + exhaustive handling (e.g., for state, events, API responses)
- branded-types.md - Nominal types for ID/unit safety (e.g., UserId vs OrderId)
- template-literals.md - String pattern types (e.g., event names, CSS values, route parameters)
- type-testing.md - Type-level testing with
*.test-d.ts files
- setup.md - tsconfig, strict options, ts-reset configuration
Notes
- These guidelines complement, not replace, project-specific conventions
- When in doubt, prioritize readability and maintainability
- Runtime type validation (zod, arktype) is recommended for external data
- Avoid over-engineering types; simple is better than clever
1---2name: typescript-best-practices3description: TypeScript coding guidelines with dos and don'ts for type design and patterns. Use when writing, reviewing, or refactoring TypeScript code in projects with tsconfig.json or .ts/.tsx files. Trigger when the user asks to "review TypeScript code", "check my TS code", "write TypeScript", or when creating or modifying any .ts/.tsx file. Also applies when discussing type design, generics, naming conventions, interface vs type decisions, or TypeScript patterns. Boundary: not for JavaScript-only projects without TypeScript configuration.4---56# TypeScript Best Practices78Guidelines for writing clean, type-safe, and maintainable TypeScript code.910> **Note:** If the repository has established code style conventions, follow those first. These guidelines serve as defaults.1112## Core Principles13141. **Type-First Design** - Define types before implementation; minimize reliance on inference152. **Interface for Structure** - Use `interface` for objects, `type` for unions/mapped/conditional163. **Namespace for Type Organization** - Group related types with namespaces (types only, not runtime)174. **Generic Const for Strictness** - Use `<const TConfig>` for strict literal inference185. **Extract, Don't Redefine** - Get types from existing definitions instead of duplicating196. **Strictest Config** - Use strictest tsconfig base; install `ts-reset` for saner built-in types2021## Quick Reference2223### interface vs type2425| Use | When |26|-----|------|27| `interface` | Object structures, class contracts, extensible APIs |28| `type` | Union types, mapped types, conditional types, tuples |2930### Naming Conventions3132| Element | Convention | Example |33|---------|------------|---------|34| Interface/Type | PascalCase | `UserProfile`, `ResponseData` |35| Generic parameters | `T` prefix | `TUser`, `TConfig` (never bare `T`, `K`, `V`) |36| Acronyms | First cap only | `userId`, `ApiResponse` (NOT `userID`, `APIResponse`) |37| Constants | UPPER_SNAKE | `MAX_RETRY_COUNT` |38| Variables/Functions | camelCase | `getUserById`, `isActive` |3940### Array Syntax4142| DO | DON'T |43|----|-------|44| `Array<TItem>` | `TItem[]` |45| `ReadonlyArray<TItem>` | `readonly TItem[]` |4647### Object Types4849| Use Case | DO | DON'T |50|----------|-----|-------|51| Empty object | `Record<string, never>` | `{}` |52| Any object (extends) | `Record<string, any>` | `Record<string, unknown>` |53| Any object (annotation) | `Record<string, unknown>` | `Record<string, any>` |54| Non-primitive | `object` | `{}` |5556### Assertions5758| DO | DON'T |59|----|-------|60| Zod/arktype for runtime validation | `response as User` |61| `satisfies` for compile-time checks | `value as unknown as Type` |62| Type guards (`if ('prop' in obj)`) | `as any` to silence errors |63| Explicit null checks | `x!` non-null assertion |6465### Function Declarations6667```typescript68// DO: Type on the const69const myFunction: myFunction.Type = (options) => {70 // implementation71};7273// DO: satisfies when namespace doesn't exist74const onClick = ((event) => {75 // implementation76}) satisfies React.ComponentProps<'button'>['onClick'];77```7879### Type Extraction8081```typescript82// DO: Extract from existing definitions83type OnClick = React.ComponentProps<'button'>['onClick'];84type ItemIds = Array<Item['id']>;85type TimeoutType = NonNullable<typeof config['timeout']>;8687// DON'T: Manually redefine types88type BadItemIds = Array<number>; // Won't update if Item.id changes89```9091## Summary Checklist9293Before committing TypeScript code, verify:9495- [ ] Used `interface` for object types, `type` for unions/mapped/conditional96- [ ] No `as` or `!` assertions — use Zod, `satisfies`, type guards, or explicit null checks97- [ ] Branded types use Zod `.brand()` or type-fest `Tagged` (not manual casting)98- [ ] Naming follows conventions (PascalCase types, `T` prefix for generics, `Id` not `ID`)99- [ ] Types extracted from existing definitions where possible100- [ ] Functions use namespace pattern for complex type organization101- [ ] Arrow functions for const declarations102- [ ] Complex generics have type tests103104## Reference Files105106For detailed patterns and examples, see:107108- **[type-patterns.md](references/type-patterns.md)** - Type syntax, assertions, namespace pattern, generics109- **[code-style.md](references/code-style.md)** - Safe array access, early return, avoid destructuring, avoid enum110- **[union-exhaustive.md](references/union-exhaustive.md)** - Discriminated unions + exhaustive handling (e.g., for state, events, API responses)111- **[branded-types.md](references/branded-types.md)** - Nominal types for ID/unit safety (e.g., UserId vs OrderId)112- **[template-literals.md](references/template-literals.md)** - String pattern types (e.g., event names, CSS values, route parameters)113- **[type-testing.md](references/type-testing.md)** - Type-level testing with `*.test-d.ts` files114- **[setup.md](references/setup.md)** - tsconfig, strict options, ts-reset configuration115116## Notes117118- These guidelines complement, not replace, project-specific conventions119- When in doubt, prioritize readability and maintainability120- Runtime type validation (zod, arktype) is recommended for external data121- Avoid over-engineering types; simple is better than clever