| name | typescript-expert |
| description | Use this skill when writing, reviewing, or refactoring TypeScript code for type safety, idiomatic patterns, and modern best practices. |
| metadata | {"applyTo":"**/*.ts, **/*.tsx"} |
TypeScript Expert Skill
Universal TypeScript best practices for type safety, maintainability, and performance.
Core Philosophy
Types are documentation the compiler enforces.
Write types that make illegal states unrepresentable. Prefer compile-time errors over runtime errors.
1. Type Safety Fundamentals
Enable Strict Mode
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true
}
}
Prefer unknown Over any
function process(data: any) {
return data.foo.bar;
}
function process(data: unknown) {
if (typeof data === "object" && data !== null && "foo" in data) {
}
}
Use as const for Literal Types
const req = { method: "GET" };
const req = { method: "GET" } as const;
const colors = ["red", "green", "blue"] as const;
Type Narrowing
function format(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
function move(animal: Fish | Bird) {
if ("swim" in animal) return animal.swim();
return animal.fly();
}
function isString(value: unknown): value is string {
return typeof value === "string";
}
2. Discriminated Unions
Model state and variants with a shared discriminant property:
type Result<T, E = Error> =
| { status: "ok"; data: T }
| { status: "error"; error: E };
function handle(result: Result<User>) {
if (result.status === "ok") {
console.log(result.data);
} else {
console.log(result.error);
}
}
Exhaustiveness with never
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
default:
const _exhaustive: never = shape;
throw new Error(`Unhandled: ${_exhaustive}`);
}
}
3. Generics
Constrain Properly
function getProp<T>(obj: T, key: string) {
return obj[key];
}
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
Provide Defaults
type Response<T = unknown, E = Error> = {
data: T | null;
error: E | null;
};
Generics vs Overloads
function parse(input: string): string;
function parse(input: number): number;
function parse(input: string | number) { return input; }
function parse<T extends string | number>(input: T): T {
return input;
}
Keep Generics Minimal
function greet<T>(name: string): string { return `Hello, ${name}`; }
function greet(name: string): string { return `Hello, ${name}`; }
4. Utility Types
Partial<T>
Required<T>
Pick<T, K>
Omit<T, K>
Record<K, V>
NonNullable<T>
ReturnType<F>
Parameters<F>
Awaited<T>
Exclude<T, U>
Extract<T, U>
Common Patterns
type Draft<T> = Partial<T>;
type UserPreview = Pick<User, "id" | "name">;
type PublicUser = Omit<User, "password" | "email">;
type UserMap = Record<string, User>;
type Defined<T> = NonNullable<T>;
5. Advanced Type Patterns
Template Literal Types
type EventName = `on${Capitalize<"click" | "focus">}`;
type Route = `/${string}`;
type CSSValue = `${number}${"px" | "rem" | "%"}`;
Conditional Types with infer
type Return<T> = T extends (...args: never[]) => infer R ? R : never;
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type Element<T> = T extends (infer E)[] ? E : never;
Mapped Types
type Immutable<T> = { readonly [K in keyof T]: T[K] };
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
6. Module Patterns
Type-Only Imports
import type { User, Config } from "./types";
import { createUser } from "./users";
import { createUser, type User } from "./users";
Enable verbatimModuleSyntax
{
"compilerOptions": {
"verbatimModuleSyntax": true
}
}
7. Interface vs Type
Use interface for | Use type for |
|---|
| Object shapes | Unions |
| Extensible contracts | Intersections |
| Class implementations | Mapped types |
| Declaration merging | Function types |
interface User {
id: string;
name: string;
}
type Status = "active" | "inactive";
type UserWithStatus = User & { status: Status };
8. Function Signatures
Callbacks: Use void, Not any
function run(cb: () => any) { cb(); }
function run(cb: () => void) { cb(); }
Overloads: Specific First
declare function fn(x: unknown): unknown;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: unknown): unknown;
Prefer Union Over Multiple Overloads
interface Moment {
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}
interface Moment {
utcOffset(b: number | string): Moment;
}
9. Error Handling
Result Type Pattern
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return { ok: false, error: "Division by zero" };
return { ok: true, value: a / b };
}
const result = divide(10, 2);
if (result.ok) {
console.log(result.value);
} else {
console.error(result.error);
}
Typed Custom Errors
class ValidationError extends Error {
constructor(
message: string,
public readonly field: string,
public readonly code: string
) {
super(message);
this.name = "ValidationError";
}
}
10. Performance
Prefer Interfaces Over Intersections
type Extended = Base & { extra: string };
interface Extended extends Base {
extra: string;
}
Enable Incremental Builds
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo"
}
}
Use Project References
{
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" }
]
}
Quick Reference
| You're doing... | Don't use | Use instead |
|---|
| Unknown input | any | unknown + narrowing |
| Operation result | T | null | Discriminated union |
| Immutable value | let | as const |
| Object shape | type | interface (if extendable) |
| Union/mapped | interface | type |
| Callback return | any | void |
| Type assertion | as Type | Type guard or narrowing |
| Deep nesting | Manual recursion | Utility types |
Red Flags
| Pattern | Problem | Fix |
|---|
any | Disables type checking | Use unknown |
as Type | Hides type errors | Narrow properly |
! non-null | Runtime error risk | Handle null case |
// @ts-ignore | Suppresses real errors | Fix the type |
| Unused generic | Adds complexity | Remove it |
Object type | Wrong type | Use object |
References