typescript-best-practices
Skill0 starsChanged 4 months agoMITView on GitHub
TypeScript code quality and performance optimization guidelines for writing type-safe, maintainable code. Use when writing new TypeScript files, reviewing existing code, refactoring to improve type safety, optimizing build performance, or setting up TypeScript configuration. Triggers on tasks involving TypeScript, tsconfig, type definitions, generics, or type system design.
77 other projects use a version of this.
Adopted by
Most recently taken up by
- Yeachan-Heo/oh-my-claude-sisyphustheir own version, first seen Nov 8, 2026
- caidanw/skillstheir own version, first seen Nov 8, 2026
- trpc/trpctheir own version, first seen Nov 8, 2026
- Yeachan-Heo/oh-my-claudecodetheir own version, first seen Nov 8, 2026
- endqwerty/endstacktheir own version, first seen Nov 8, 2026
- tristanphvn/keeltheir own version, first seen Nov 8, 2026
Used with
Projects with it also use setup-pstack, poteto-help and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
bnemaSkillsetup-pstack
Configure which Pi models pstack uses per role. Detects available provider/model specs and writes pstack's user configuration. Use for /setup-pstack, "configure pstack models", or changing pstack model choices.
Also in 22 projects that use this
ianclearySkillpoteto-help
Guides users through pstack setup, /poteto-mode, and picking the skill, playbook, or principle for a task. Type /poteto-help with a question.
Also in 12 projects that use this
eryzerzSkillmake-bot-ui
Use when building a custom UI (page, dashboard, buttons) that should wake a Grok Bot over a webhook, when the user must provide a webhook sender key, or when exposing that UI on Tailscale.
Also in 14 projects that use this
ianclearySkillprinciple-explain-the-number
Apply before you trust, report, or act on a number you measured: a speedup, a regression, a throughput, a latency, or an eval result. Find what limits it, and rule out that it measured something other than the work you think.
Also in 20 projects that use this
tristanphvnSkillketchup-video
Catch the user up like /keel:ketchup, with a short silent video of slides for what happened and the waiting calls as text under it. Use for /keel:ketchup-video.
Also in 8 projects that use this
instructureSkillketchup
Rebuild context after /clear by analyzing uncommitted changes across Canvas LMS and all plugin repositories
Also in 17 projects that use this
Other versions77 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 77
- trpc/trpc★ 41ksame name
- Yeachan-Heo/oh-my-claude-sisyphus★ 41ksame name
- Yeachan-Heo/oh-my-claudecode★ 41ksame name
- backnotprop/pstack★ 3.8ksame name
- jtaroreh/agystack★ 128same name
- uzairansaruzi/p3-stack★ 126same name
- dsebban/skills★ 71same name
- baekenough/oh-my-customcode★ 42same name
- overextended/skills★ 22same name
- TheOnlyFusionCube/potetos-for-everyone★ 21same name
67 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository3 other files it gives its agents.
The file
We split this skill into the rules it gives an agent
11 rules. 0 are common, 5 are distinctive, and 6 are very specific to this project.
0
Common
Most of the 26 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 36
When the user explicitly asks for non-TypeScript solutions
- Distinctive: line 42
Before writing code, ensure:
- Very specific: line 194
Import @total-typescript/ts-reset for better array and Promise type inference.
- Very specific: line 268
Use @ts-expect-error instead of @ts-ignore when you must suppress an error.
- Distinctive: line 269
Keep functions small and focused.
- Very specific: line 270
Prefer interface over type for public API shapes.
- Very specific: line 271
Use const assertions for constant tuples and configurations.
- Very specific: line 273
Write unit tests for your type definitions using expectTypeOf from vitest or ts-expect.
- Distinctive: line 280
Complex generic signatures: If a generic type signature is hard to read, it is probably too complex.
- Very specific: line 282
Defining types in the same file as implementation: Keep type definitions in separate .types.ts files for better organization.
- Distinctive: line 286
When this skill is triggered, the agent should produce:
“Common” is measured: kept by at least 6 in 10 of the 26 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
TypeScript Best Practices
Act as a TypeScript expert advisor. Provide guidance on writing type-safe, performant, and maintainable TypeScript code following community best practices and the official TypeScript team recommendations.
Purpose
This skill helps AI agents produce high-quality TypeScript code that leverages the type system effectively while avoiding common pitfalls. It covers type system design, configuration optimization, performance patterns, and project structure.
When to Use
- Writing new TypeScript files or modules
- Reviewing existing TypeScript code for type safety issues
- Refactoring JavaScript to TypeScript
- Configuring
tsconfig.jsonfor a project - Designing type definitions or generic APIs
- Optimizing TypeScript build and compilation performance
- Debugging complex type errors
- Setting up strict mode and linting integration
- Working with union/discriminated union types
- Implementing advanced patterns (branded types, type guards, assertion functions)
When NOT to Use
- Writing pure JavaScript code that will not be migrated to TypeScript
- Projects using a different type system (Flow, ReasonML)
- Simple type annotations that are obvious (the agent should already handle these)
- When the user explicitly asks for non-TypeScript solutions
Instructions
1. Configuration First
Before writing code, ensure:
strict: trueis enabled intsconfig.json. Without it, the type system is significantly weaker.noUncheckedIndexedAccess: truefor safer object access.exactOptionalPropertyTypes: truefor stricter optional property handling.verbatimModuleSyntax: truefor consistent module syntax.
2. Type System Design
Prefer interfaces over type aliases for object shapes
Interfaces are more extensible and produce clearer error messages. Use type for unions, intersections, mapped types, and utility patterns.
// Prefer
interface User {
id: string;
name: string;
}
// Use type for
type Status = 'active' | 'inactive' | 'pending';
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };Use branded types for domain primitives
Prevent mixing up values that share the same underlying type but represent different concepts.
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function getUser(id: UserId): User { /* ... */ }
function getOrder(id: OrderId): Order { /* ... */ }
// TypeScript prevents: getUser(orderId)Leverage discriminated unions for state management
Model states explicitly so invalid states are unrepresentable.
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function renderState(state: RequestState<User>) {
switch (state.status) {
case 'idle': return null;
case 'loading': return <Spinner />;
case 'success': return <UserProfile user={state.data} />;
case 'error': return <ErrorMessage error={state.error} />;
}
}Prefer type inference over explicit annotations
Let TypeScript infer types when the type is obvious from context. Add explicit annotations at module boundaries (function signatures, exported API surface).
// Good: inferred
const items = [1, 2, 3];
// Good: explicit at boundary
export function processItems(items: number[]): Result[] { /* ... */ }
// Unnecessary
const name: string = 'hello';3. Generics and Utility Types
Constrain generics with the minimal required shape
// Instead of
function clone<T>(obj: T): T { return JSON.parse(JSON.stringify(obj)); }
// Prefer
function clone<T extends Record<string, unknown>>(obj: T): T { return JSON.parse(JSON.stringify(obj)); }Use satisfies for type validation without widening
const palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;
// palette.red is number[] (not string | number[])
// palette.green is string (not string | number[])4. Strictness Patterns
Use as const for literal types and readonly tuples
const COLORS = ['red', 'green', 'blue'] as const;
type Color = (typeof COLORS)[number]; // 'red' | 'green' | 'blue'Prefer unknown over any
unknown forces type checking before use. Reserve any for migration scenarios only.
// Prefer
function parseJSON(input: string): unknown {
return JSON.parse(input);
}
// Avoid
function parseJSON(input: string): any {
return JSON.parse(input);
}Use type guards and assertion functions
function isError(value: unknown): value is Error {
return value instanceof Error;
}
function assertString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new TypeError('Expected string');
}
}5. Performance Optimization
Prefer interfaces for object types
Interfaces are faster for the compiler to check than type aliases due to caching.
Avoid large union types
Break large unions (100+ members) into smaller, composable types. Consider using branded types or enums for finite sets.
Use ts-reset or explicit narrowing
Import @total-typescript/ts-reset for better array and Promise type inference. Otherwise, manually narrow with explicit checks.
Avoid excessive conditional types
Deeply nested conditional types slow down compilation. Prefer mapped types or lookup types when possible.
6. Error Handling
Use Result types for expected failures
type Result<T, E = Error> = { success: true; value: T } | { success: false; error: E };
function divide(a: number, b: number): Result<number> {
if (b === 0) return { success: false, error: new Error('Division by zero') };
return { success: true, value: a / b };
}Use typed try/catch with error handling
try {
await riskyOperation();
} catch (error) {
if (error instanceof ApiError) {
handleApiError(error);
} else if (error instanceof NetworkError) {
retry();
} else {
throw error;
}
}Examples
Before (untyped)
function process(data) {
return data.map(item => item.value);
}After (type-safe)
interface DataItem {
id: string;
value: number;
}
function process(data: DataItem[]): number[] {
return data.map(item => item.value);
}API Response Handling
interface ApiResponse<T> {
data: T;
timestamp: string;
version: number;
}
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
if (!response.ok) throw new ApiError(response.status, await response.text());
return response.json();
}Best Practices
- Enable
strict: truein every project. This catches the majority of type errors. - Use
@ts-expect-errorinstead of@ts-ignorewhen you must suppress an error.@ts-expect-errorwill warn you if the error is no longer relevant, while@ts-ignoresilently suppresses forever. - Keep functions small and focused. TypeScript's type inference works best with small, pure functions.
- Prefer
interfaceovertypefor public API shapes. Interfaces are more extensible and produce better editor tooling. - Use
constassertions for constant tuples and configurations. - Export types that consumers of your library or module need.
- Write unit tests for your type definitions using
expectTypeOffromvitestorts-expect.
Common Mistakes
- Using
anyinstead ofunknown:anydisables type checking entirely. Always preferunknownand narrow the type before use. - Over-using type assertions (
as): Type assertions hide type errors. Prefer type guards and constructor functions. - Missing
strict: true: Without strict mode, TypeScript misses null checks, implicit any, and other common errors. - Complex generic signatures: If a generic type signature is hard to read, it is probably too complex. Break it down or use simpler patterns.
- Ignoring
noUncheckedIndexedAccess: Accessing object properties without checking forundefinedleads to runtime errors. - Defining types in the same file as implementation: Keep type definitions in separate
.types.tsfiles for better organization.
Expected Output
When this skill is triggered, the agent should produce:
- Type-safe code with proper annotations at module boundaries
- Fully configured
tsconfig.jsonwith strict mode enabled - Discriminated unions for state management where applicable
- Branded types for domain primitives to prevent type confusion
- Proper error handling with typed error variants
- Clean, readable generics constrained to the minimum required shape
- Inline documentation only where the type signature is non-obvious
Limitations
- This skill does not cover runtime validation libraries like Zod or Yup (consider installing a separate skill for those).
- This skill does not cover framework-specific TypeScript patterns for React, Next.js, or Angular.
- TypeScript compilation performance depends on project size; this skill provides general guidance but cannot fix inherent scalability limits of the TypeScript compiler.
- Some pattern recommendations may conflict with existing codebase conventions; always respect the project's established style.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

