TypeScript patterns covering strict types, generics constraints, utility types (Pick, Omit, Record, Extract), discriminated unions, async patterns, and common mistakes with BAD/GOOD comparisons.
BAD: Using any defeats the type system.
function processData(data: any) {
return data.value.toUpperCase();
}
GOOD: Define explicit types or use generics.
interface DataWithValue {
value: string;
}
function processData(data: DataWithValue): string {
return data.value.toUpperCase();
}
BAD: Redundant type annotations.
const count: number = 42;
const name: string = 'Alice';
GOOD: Let TypeScript infer.
const count = 42; // inferred as number
const name = 'Alice'; // inferred as string
BAD: Using object or {}. These allow any object.
GOOD: Use Record<PropertyKey, unknown> for arbitrary objects.
function logData(data: Record<PropertyKey, unknown>) {
console.log(data);
}
GOOD: Use interface for object shapes. Use type for unions, intersections, and primitives.
// Object shapes: interface
interface User {
id: number;
name: string;
}
// Unions: type
type Status = 'pending' | 'approved' | 'rejected';
// Intersections: type
type AdminUser = User & { role: 'admin' };
BAD: Unconstrained generics allow any type.
function getProperty<T>(obj: T, key: string) {
return obj[key]; // Error: key is not guaranteed to exist on T
}
GOOD: Constrain generics with extends.
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: string
function findById<T extends Identifiable & Named>(
items: T[],
id: number
): T | undefined {
return items.find(item => item.id === id);
}
function createArray<T = string>(length: number, value: T): T[] {
return Array(length).fill(value);
}
Pick, Omit, Partial, Required, Record, Extract, Exclude, ReturnType. Full reference with BAD/GOOD pairs for each.
See references/utility-types.md for the complete utility types reference.
BAD: Unions without discriminator. TypeScript cannot narrow types.
type Result = { data: string } | { error: Error };
GOOD: Add a discriminator property.
type Success = { status: 'success'; data: string };
type Failure = { status: 'error'; error: Error };
type Result = Success | Failure;
function handleResult(result: Result) {
if (result.status === 'success') {
console.log(result.data); // TypeScript knows this is Success
} else {
console.error(result.error); // TypeScript knows this is Failure
}
}
function handleStatus(status: Status): string {
switch (status) {
case 'pending': return 'Awaiting review';
case 'approved': return 'Approved';
case 'rejected': return 'Rejected';
default:
const _exhaustive: never = status; // Error if a case is missing
throw new Error(`Unhandled status: ${_exhaustive}`);
}
}
async/await, Promise.all for concurrency, error handling with try/catch, Promise.allSettled for partial failures, timeout wrappers.
See references/async-patterns.md for full patterns with BAD/GOOD examples.
BAD: Hardcoded strings and numbers.
GOOD: Use named constants.
const USER_STATUS = {
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected',
} as const;
BAD: Using as any to bypass type checking.
GOOD: Use @ts-expect-error for known issues. Forces re-evaluation when fixed.
Prefer for...of over index loops, async APIs over *Sync variants, ?? over || for defaults, destructuring for clarity.
See references/performance-tips.md for the complete set of performance patterns.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer