TypeScript Cheatsheet
Comprehensive quick reference for TypeScript syntax, types, features, and best practices. Essential guide for developers working with TypeScript 5.0+.
Table of Contents
- Prerequisites
- Basic Types
- Type Annotations
- Functions
- Classes
- Interfaces & Types
- Generics
- Advanced Types
- Type Guards & Narrowing
- Modules & Imports
- Decorators
- Utility Types
- Configuration
- Best Practices
- Common Pitfalls
Prerequisites
TypeScript Version: This cheatsheet targets TypeScript 5.0+. Some features require specific versions (noted inline).
# Install TypeScript globallypnpm add -g typescript
# Check TypeScript versiontsc --version# β Version 5.3.3
# Install TypeScript in projectpnpm add -D typescript
# Initialize TypeScript configtsc --initNode.js: TypeScript 5.0+ requires Node.js 14.17+ or Node.js 16+ for optimal performance.
Basic Types
Primitive Types π’
// Booleanlet isActive: boolean = true;let isDone: boolean = false;
// Number (all numbers are floating point)let count: number = 42;let price: number = 19.99;let hex: number = 0xf00d;let binary: number = 0b1010;let octal: number = 0o744;
// Stringlet name: string = "John";let message: string = `Hello, ${name}!`; // Template literals
// Null and Undefinedlet nullValue: null = null;let undefinedValue: undefined = undefined;
// Symbol (ES6)let sym: symbol = Symbol("key");Array Types π
// Array syntaxlet numbers: number[] = [1, 2, 3];let names: string[] = ["Alice", "Bob"];
// Generic array syntaxlet items: Array<number> = [1, 2, 3];let users: Array<string> = ["Alice", "Bob"];
// Readonly arrayslet readonlyNumbers: ReadonlyArray<number> = [1, 2, 3];readonlyNumbers.push(4); // β Error: Property 'push' does not exist
// Tuple (fixed-length array with specific types)let tuple: [string, number] = ["hello", 10];tuple[0] = "world"; // β
Validtuple[1] = 20; // β
Validtuple[2] = true; // β Error: Type 'true' is not assignable to type 'undefined'
// Named tuple (TypeScript 4.0+)let namedTuple: [name: string, age: number] = ["John", 30];
// Optional tuple elements (TypeScript 4.0+)let optionalTuple: [string, number?] = ["hello"];optionalTuple = ["world", 42]; // β
ValidObject Types ποΈ
// Object type annotationlet user: { name: string; age: number } = { name: "John", age: 30,};
// Optional propertieslet config: { apiKey?: string; timeout: number } = { timeout: 5000,};
// Readonly propertieslet point: { readonly x: number; readonly y: number } = { x: 0, y: 0 };point.x = 10; // β Error: Cannot assign to 'x' because it is a read-only property
// Index signatureslet dictionary: { [key: string]: number } = { apples: 5, oranges: 10,};Union & Intersection Types π
// Union type (either/or)let value: string | number = "hello";value = 42; // β
Validvalue = true; // β Error: Type 'boolean' is not assignable
// Union with null/undefinedlet maybeString: string | null | undefined = "hello";maybeString = null; // β
Valid
// Intersection type (combines all)type A = { a: number };type B = { b: string };type C = A & B; // { a: number; b: string }
let obj: C = { a: 1, b: "hello" }; // β
ValidLiteral Types π
// String literal typestype Direction = "north" | "south" | "east" | "west";let dir: Direction = "north"; // β
Validdir = "up"; // β Error: Type '"up"' is not assignable
// Numeric literal typestype DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;let roll: DiceRoll = 4; // β
Valid
// Boolean literal typestype Success = true;let success: Success = true; // β
Validsuccess = false; // β Error: Type 'false' is not assignableEnum Types π―
// Numeric enum (default)enum Status { Pending, // 0 Approved, // 1 Rejected, // 2}
let status: Status = Status.Approved; // β 1
// Numeric enum with custom valuesenum HttpStatus { OK = 200, NotFound = 404, ServerError = 500,}
// String enumenum Color { Red = "red", Green = "green", Blue = "blue",}
let color: Color = Color.Red; // β "red"
// Const enum (inlined at compile time)const enum Direction { Up, Down,}
let dir = Direction.Up; // β 0 (inlined, no object created)Type Annotations
Variable Annotations π
// Explicit type annotationlet count: number = 10;
// Type inference (preferred when type is obvious)let name = "John"; // β type: stringlet age = 30; // β type: number
// Type assertion (tell TypeScript you know better)let value: unknown = "hello";let str1: string = value as string; // Angle bracket syntaxlet str2: string = <string>value; // Alternative syntax (not in JSX)
// Non-null assertion (asserts value is not null/undefined)let element: HTMLElement | null = document.getElementById("app");element!.innerHTML = "Hello"; // Use ! to assert non-null
// Const assertion (TypeScript 3.4+)let tuple = [1, "hello"] as const; // β readonly [1, "hello"]tuple[0] = 2; // β Error: Cannot assign to '0' because it is a read-only propertyType Aliases π·οΈ
// Basic type aliastype ID = string | number;let userId: ID = "123";let productId: ID = 456;
// Object type aliastype User = { id: number; name: string; email?: string; // Optional property};
// Union type aliastype Status = "pending" | "approved" | "rejected";
// Intersection type aliastype Admin = User & { permissions: string[] };
// Generic type aliastype Container<T> = { value: T };let numberContainer: Container<number> = { value: 42 };Functions
Function Declarations π€
// Function with type annotationsfunction add(a: number, b: number): number { return a + b;}
// Optional parametersfunction greet(name: string, title?: string): string { return title ? `${title} ${name}` : name;}greet("John"); // β
Validgreet("John", "Dr."); // β
Valid
// Default parametersfunction multiply(a: number, b: number = 1): number { return a * b;}multiply(5); // β 5 (b defaults to 1)multiply(5, 3); // β 15
// Rest parametersfunction sum(...numbers: number[]): number { return numbers.reduce((acc, n) => acc + n, 0);}sum(1, 2, 3); // β 6
// Function overloadsfunction format(value: string): string;function format(value: number): string;function format(value: string | number): string { return String(value);}format("hello"); // β
Validformat(42); // β
ValidArrow Functions β‘οΈ
// Arrow function with typesconst add = (a: number, b: number): number => a + b;
// Arrow function with block bodyconst multiply = (a: number, b: number): number => { return a * b;};
// Arrow function type annotationtype MathOperation = (a: number, b: number) => number;const subtract: MathOperation = (a, b) => a - b;Function Types π§
// Function type syntaxtype Handler = (event: string) => void;const onClick: Handler = (event) => { console.log(event);};
// Function interfaceinterface Calculator { (a: number, b: number): number;}
const add: Calculator = (a, b) => a + b;
// Method signature in interfaceinterface UserService { getUser(id: number): Promise<User>; updateUser(id: number, data: Partial<User>): Promise<void>;}
// Callable objectinterface Callable { (): string; description: string;}
const func: Callable = (() => { const fn = () => "Hello"; fn.description = "Greeting function"; return fn;})();This Parameter π―
// Explicit this parameterfunction handler(this: { name: string }, message: string) { console.log(`${this.name}: ${message}`);}
const context = { name: "App" };handler.call(context, "Hello"); // β
Validhandler("Hello"); // β Error: The 'this' context is not assignable
// Arrow functions preserve this from outer scopeclass Component { name = "Component";
handleClick() { // Regular function - this is bound to Component setTimeout(function () { console.log(this.name); // β Error: 'this' is Window }, 100);
// Arrow function - this is preserved setTimeout(() => { console.log(this.name); // β
"Component" }, 100); }}Classes
Class Basics ποΈ
// Basic classclass User { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; }
greet(): string { return `Hello, I'm ${this.name}`; }}
// Parameter properties (shorthand)class User { constructor( public name: string, public age: number, private email: string, ) {}}
// Equivalent to:class User { name: string; age: number; private email: string;
constructor(name: string, age: number, email: string) { this.name = name; this.age = age; this.email = email; }}Access Modifiers π
class BankAccount { public balance: number; // Accessible everywhere private accountNumber: string; // Only accessible within class protected owner: string; // Accessible in class and subclasses
constructor(accountNumber: string, owner: string) { this.accountNumber = accountNumber; this.owner = owner; this.balance = 0; }
// Private method private validateAmount(amount: number): boolean { return amount > 0; }
deposit(amount: number): void { if (this.validateAmount(amount)) { this.balance += amount; } }}
// Readonly modifierclass Point { readonly x: number; readonly y: number;
constructor(x: number, y: number) { this.x = x; this.y = y; }}
const point = new Point(1, 2);point.x = 3; // β Error: Cannot assign to 'x' because it is a read-only propertyStatic Members β‘
class MathUtils { static PI = 3.14159;
static add(a: number, b: number): number { return a + b; }}
// Access static members without instanceMathUtils.PI; // β 3.14159MathUtils.add(1, 2); // β 3
// Static blocks (TypeScript 4.7+)class Config { static apiKey: string;
static { // Initialize static members this.apiKey = process.env.API_KEY || "default"; }}Inheritance & Abstract Classes π§¬
// Base classclass Animal { constructor(public name: string) {}
move(distance: number = 0): void { console.log(`${this.name} moved ${distance}m`); }}
// Derived classclass Dog extends Animal { constructor( name: string, public breed: string, ) { super(name); // Must call super() first }
bark(): void { console.log("Woof!"); }
// Override method move(distance: number = 5): void { console.log("Running..."); super.move(distance); }}
// Abstract class (cannot be instantiated)abstract class Shape { abstract getArea(): number;
display(): void { console.log(`Area: ${this.getArea()}`); }}
class Circle extends Shape { constructor(private radius: number) { super(); }
getArea(): number { return Math.PI * this.radius ** 2; }}
const circle = new Circle(5);circle.display(); // β "Area: 78.54"Getters & Setters ποΈ
class Temperature { private _celsius: number = 0;
get celsius(): number { return this._celsius; }
set celsius(value: number) { if (value < -273.15) { throw new Error("Temperature below absolute zero"); } this._celsius = value; }
get fahrenheit(): number { return (this._celsius * 9) / 5 + 32; }
set fahrenheit(value: number) { this._celsius = ((value - 32) * 5) / 9; }}
const temp = new Temperature();temp.celsius = 25; // Uses setterconsole.log(temp.fahrenheit); // β 77 (uses getter)Interfaces & Types
Interface Basics π
// Basic interfaceinterface User { id: number; name: string; email: string;}
// Optional propertiesinterface Config { apiKey: string; timeout?: number; // Optional retries?: number; // Optional}
// Readonly propertiesinterface Point { readonly x: number; readonly y: number;}
// Index signaturesinterface Dictionary { [key: string]: number;}
const scores: Dictionary = { alice: 95, bob: 87,};Interface Extends & Merging π
// Interface extensioninterface Animal { name: string;}
interface Dog extends Animal { breed: string;}
// Multiple extensioninterface A { a: number;}interface B { b: string;}interface C extends A, B { c: boolean;}
// Declaration merging (interfaces only)interface Window { customProperty: string;}
interface Window { anotherProperty: number;}
// Result: Window has both customProperty and anotherPropertyType vs Interface π€
// Interface - extensible, can be mergedinterface User { name: string;}
interface User { age: number; // Merges with previous declaration}
// Type - can use unions, intersections, primitivestype Status = "pending" | "approved" | "rejected";type ID = string | number;type UserType = { name: string; age: number;};
// Type cannot be mergedtype User = { name: string };type User = { age: number }; // β Error: Duplicate identifier
// Interface for object shapes (preferred)interface ApiResponse { data: unknown; status: number;}
// Type for unions, intersections, primitives (preferred)type ApiMethod = "GET" | "POST" | "PUT" | "DELETE";type ApiRequest = { method: ApiMethod; url: string;} & ApiResponse;Generics
Generic Functions π
// Basic generic functionfunction identity<T>(arg: T): T { return arg;}
const str = identity<string>("hello"); // β "hello"const num = identity<number>(42); // β 42const inferred = identity("world"); // Type inferred as string
// Generic with constraintsfunction getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key];}
const user = { name: "John", age: 30 };const name = getProperty(user, "name"); // β "John"const invalid = getProperty(user, "email"); // β Error: Argument of type '"email"' is not assignable
// Multiple type parametersfunction pair<T, U>(first: T, second: U): [T, U] { return [first, second];}
const result = pair("hello", 42); // β ["hello", 42]Generic Classes ποΈ
// Generic classclass Container<T> { private items: T[] = [];
add(item: T): void { this.items.push(item); }
get(index: number): T { return this.items[index]; }
getAll(): T[] { return this.items; }}
const stringContainer = new Container<string>();stringContainer.add("hello");stringContainer.add("world");
const numberContainer = new Container<number>();numberContainer.add(1);numberContainer.add(2);Generic Constraints π
// Constraint with extendsinterface HasLength { length: number;}
function logLength<T extends HasLength>(item: T): void { console.log(item.length);}
logLength("hello"); // β
Valid (string has length)logLength([1, 2, 3]); // β
Valid (array has length)logLength(42); // β Error: number doesn't have length
// Constraint with keyoffunction getValue<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key];}
const user = { name: "John", age: 30 };const name = getValue(user, "name"); // β "John"
// Default type parametersinterface ApiResponse<T = unknown> { data: T; status: number;}
const response1: ApiResponse = { data: "any", status: 200 };const response2: ApiResponse<User> = { data: user, status: 200 };Conditional Types π
// Basic conditional typetype IsArray<T> = T extends any[] ? true : false;type Test1 = IsArray<string[]>; // β truetype Test2 = IsArray<string>; // β false
// Infer keywordtype ArrayElement<T> = T extends (infer U)[] ? U : never;type Element = ArrayElement<string[]>; // β string
// Extract return typetype ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Distributive conditional typestype ToArray<T> = T extends any ? T[] : never;type StrArrOrNumArr = ToArray<string | number>; // β string[] | number[]Advanced Types
Mapped Types π
// Make all properties optionaltype Partial<T> = { [P in keyof T]?: T[P];};
// Make all properties readonlytype Readonly<T> = { readonly [P in keyof T]: T[P];};
// Make all properties requiredtype Required<T> = { [P in keyof T]-?: T[P];};
// Pick specific propertiestype Pick<T, K extends keyof T> = { [P in K]: T[P];};
// Omit specific properties (TypeScript 3.5+)type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Custom mapped typetype Stringify<T> = { [K in keyof T]: string;};
type User = { id: number; name: string };type StringUser = Stringify<User>; // β { id: string; name: string }Template Literal Types π€
// Basic template literal typetype EventName = "click" | "hover";type HandlerName = `on${Capitalize<EventName>}`;// β "onClick" | "onHover"
// String manipulation typestype Uppercase<S extends string> = // Built-intype Lowercase<S extends string> = // Built-intype Capitalize<S extends string> = // Built-intype Uncapitalize<S extends string> = // Built-in
// Pattern matching with infertype ExtractMethod<T> = T extends `${infer Method}_${string}` ? Method : never;
type Endpoint = "GET_users" | "POST_users";type Method = ExtractMethod<Endpoint>; // β "GET" | "POST"Recursive Types π
// Recursive type definitiontype JsonValue = | string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
// Recursive type aliastype TreeNode<T> = { value: T; children: TreeNode<T>[];};
const tree: TreeNode<string> = { value: "root", children: [ { value: "child1", children: [] }, { value: "child2", children: [] }, ],};Type Guards & Narrowing
Type Guards π‘οΈ
// Type predicate functionfunction isString(value: unknown): value is string { return typeof value === "string";}
function process(value: unknown) { if (isString(value)) { // value is narrowed to string here console.log(value.toUpperCase()); // β
Valid }}
// Type guard for custom typesinterface Cat { type: "cat"; meow: () => void;}
interface Dog { type: "dog"; bark: () => void;}
function isCat(animal: Cat | Dog): animal is Cat { return animal.type === "cat";}
function handleAnimal(animal: Cat | Dog) { if (isCat(animal)) { animal.meow(); // β
Type narrowed to Cat } else { animal.bark(); // β
Type narrowed to Dog }}Assertion Functions β
// Assertion functionfunction assertIsNumber(value: unknown): asserts value is number { if (typeof value !== "number") { throw new Error("Expected number"); }}
function process(value: unknown) { assertIsNumber(value); // value is narrowed to number after assertion console.log(value.toFixed(2)); // β
Valid}
// Assertion with conditionfunction assert(condition: unknown): asserts condition { if (!condition) { throw new Error("Assertion failed"); }}Narrowing Patterns π―
// typeof narrowingfunction process(value: string | number) { if (typeof value === "string") { console.log(value.toUpperCase()); // value is string } else { console.log(value.toFixed(2)); // value is number }}
// instanceof narrowingfunction process(value: Date | string) { if (value instanceof Date) { console.log(value.getFullYear()); // value is Date } else { console.log(value.toUpperCase()); // value is string }}
// in operator narrowinginterface Bird { fly: () => void;}
interface Fish { swim: () => void;}
function move(animal: Bird | Fish) { if ("fly" in animal) { animal.fly(); // animal is Bird } else { animal.swim(); // animal is Fish }}
// Discriminated unionstype Success = { status: "success"; data: string };type Error = { status: "error"; message: string };type Result = Success | Error;
function handle(result: Result) { if (result.status === "success") { console.log(result.data); // result is Success } else { console.log(result.message); // result is Error }}Modules & Imports
ES Modules π¦
// Export declarationsexport const PI = 3.14159;export function add(a: number, b: number): number { return a + b;}export class Calculator { // ...}
// Named exportsexport { add, Calculator };export { add as sum, Calculator as Calc };
// Default exportexport default function greet(name: string): string { return `Hello, ${name}`;}
// Re-exportexport { add } from "./math";export * from "./utils"; // Re-export all
// Type-only exports (TypeScript 3.8+)export type { User, ApiResponse };export type { User as UserType };Import Statements π₯
// Named importsimport { add, subtract } from "./math";import { add as sum } from "./math";
// Default importimport greet from "./greet";
// Mixed importsimport greet, { add, subtract } from "./utils";
// Namespace importimport * as MathUtils from "./math";MathUtils.add(1, 2);
// Type-only imports (TypeScript 3.8+)import type { User, ApiResponse } from "./types";import type { User as UserType } from "./types";
// Side-effect importimport "./polyfills"; // Executes file, no imports
// Dynamic importconst module = await import("./utils");module.add(1, 2);Module Resolution π
// Relative importsimport { utils } from "./utils";import { config } from "../config";
// Absolute imports (configured in tsconfig.json)import { api } from "@/api";import { components } from "~/components";
// Node modulesimport express from "express";import { useState } from "react";
// Path mapping example (tsconfig.json)// {// "compilerOptions": {// "baseUrl": ".",// "paths": {// "@/*": ["src/*"],// "~/*": ["src/components/*"]// }// }// }Decorators
Class Decorators π¨
// Basic class decoratorfunction sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype);}
@sealedclass Greeter { greeting: string; constructor(message: string) { this.greeting = message; }}
// Decorator factoryfunction color(value: string) { return function (constructor: Function) { // Add metadata or modify constructor };}
@color("blue")class Car { // ...}Method Decorators π§
// Method decoratorfunction enumerable(value: boolean) { return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor, ) { descriptor.enumerable = value; };}
class Greeter { @enumerable(false) greet() { return "Hello"; }}
// Property decoratorfunction format(target: any, propertyKey: string) { // Modify property metadata}
class User { @format name: string;}β οΈ Note: Decorators are experimental and require
"experimentalDecorators": truein tsconfig.json. They may change in future TypeScript versions.
Utility Types
Common Utility Types π οΈ
// Partial<T> - Make all properties optionaltype PartialUser = Partial<User>;
// Required<T> - Make all properties requiredtype RequiredUser = Required<User>;
// Readonly<T> - Make all properties readonlytype ReadonlyUser = Readonly<User>;
// Pick<T, K> - Select specific propertiestype UserName = Pick<User, "name" | "email">;
// Omit<T, K> - Remove specific propertiestype UserWithoutId = Omit<User, "id">;
// Record<K, T> - Create object type with keys K and values Ttype StatusConfig = Record<"pending" | "approved", { color: string }>;
// Exclude<T, U> - Exclude types from uniontype NonNull = Exclude<string | number | null, null>; // β string | number
// Extract<T, U> - Extract types from uniontype Numbers = Extract<string | number | boolean, number>; // β number
// NonNullable<T> - Remove null and undefinedtype DefiniteString = NonNullable<string | null | undefined>; // β string
// Parameters<T> - Extract function parameterstype Params = Parameters<(a: string, b: number) => void>; // β [string, number]
// ReturnType<T> - Extract return typetype Return = ReturnType<() => string>; // β string
// Awaited<T> - Unwrap Promise (TypeScript 4.5+)type Data = Awaited<Promise<string>>; // β stringπ Note: For comprehensive utility types reference, see the TypeScript documentation on utility types.
Configuration
tsconfig.json Essentials βοΈ
{ "compilerOptions": { // Language and Environment "target": "ES2020", // Compile target (ES3, ES5, ES2015, etc.) "module": "ESNext", // Module system (CommonJS, ES2015, ESNext) "lib": ["ES2020", "DOM"], // Include type definitions
// Module Resolution "moduleResolution": "node", // How modules are resolved "baseUrl": ".", // Base directory for module resolution "paths": { // Path mapping "@/*": ["src/*"] }, "resolveJsonModule": true, // Allow importing JSON files
// Type Checking "strict": true, // Enable all strict checks "noImplicitAny": true, // Error on implicit any "strictNullChecks": true, // Strict null checking "strictFunctionTypes": true, // Strict function types "noUnusedLocals": true, // Error on unused locals "noUnusedParameters": true, // Error on unused parameters "noImplicitReturns": true, // Error on missing returns "noFallthroughCasesInSwitch": true, // Error on switch fallthrough
// Emit "outDir": "./dist", // Output directory "rootDir": "./src", // Root directory of source files "removeComments": true, // Remove comments in output "sourceMap": true, // Generate source maps "declaration": true, // Generate .d.ts files "declarationMap": true, // Generate declaration source maps
// Interop Constraints "esModuleInterop": true, // Enable ES module interop "allowSyntheticDefaultImports": true, // Allow default imports from modules
// Advanced "skipLibCheck": true, // Skip type checking of declaration files "forceConsistentCasingInFileNames": true, // Enforce consistent casing "isolatedModules": true, // Ensure each file can be safely transpiled "incremental": true, // Enable incremental compilation "composite": true // Enable project references }, "include": ["src/**/*"], // Files to include "exclude": ["node_modules", "dist"] // Files to exclude}Common Configurations π―
// React + TypeScript{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "jsx": "react-jsx", // React 17+ JSX transform "moduleResolution": "node", "strict": true, "esModuleInterop": true, "skipLibCheck": true }}
// Node.js + TypeScript{ "compilerOptions": { "target": "ES2020", "module": "CommonJS", "lib": ["ES2020"], "moduleResolution": "node", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "outDir": "./dist", "rootDir": "./src" }}
// Library + TypeScript{ "compilerOptions": { "target": "ES2015", "module": "ESNext", "lib": ["ES2020"], "declaration": true, "declarationMap": true, "outDir": "./dist", "strict": true }}Best Practices
β Doβs
- Use type inference when types are obvious:
const name = "John"instead ofconst name: string = "John" - Prefer
typeoverinterfacefor unions, intersections, and primitives - Use
interfacefor object shapes that may be extended or merged - Enable strict mode (
"strict": true) for better type safety - Use const assertions (
as const) for literal types and readonly arrays - Use type guards for runtime type checking and narrowing
- Leverage utility types (
Partial,Pick,Omit, etc.) for type transformations - Use generic constraints (
extends) to limit type parameters - Prefer readonly for immutable data structures
- Use discriminated unions for type-safe state management
- Extract complex types into type aliases for reusability
- Use
unknowninstead ofanywhen type is truly unknown - Enable
noImplicitAnyto catch implicit any types - Use template literal types for type-safe string manipulation (TypeScript 4.1+)
- Document complex types with JSDoc comments
β Donβts
- Donβt use
any- useunknownand type guards instead - Donβt disable strict mode - it catches many potential bugs
- Donβt use type assertions (
as) without good reason - prefer type guards - Donβt mix
interfaceandtypearbitrarily - follow consistent patterns - Donβt use
@ts-ignore- fix the underlying type issue instead - Donβt use
Functiontype - use specific function signatures - Donβt use
objecttype - useRecord<string, unknown>or specific interfaces - Donβt forget optional chaining (
?.) and nullish coalescing (??) operators - Donβt use enums for simple unions - prefer union types
- Donβt ignore compiler errors - they indicate real type safety issues
- Donβt use
// @ts-expect-errorwithout explanation - Donβt create overly complex types - break them into smaller, reusable types
- Donβt use type assertions to bypass type checking - fix the types instead
β οΈ Common Pitfalls
Pitfall 1:
Readonly<T>is shallow - nested objects can still be modified
type User = { name: string; address: { city: string } };type ReadonlyUser = Readonly<User>;const user: ReadonlyUser = { name: "John", address: { city: "NYC" } };user.name = "Jane"; // β Error (good)user.address.city = "LA"; // β
No error (shallow readonly)Pitfall 2: Type inference in arrays can be too narrow
const items = [1, 2, "three"]; // β (string | number)[]// Better: const items: (string | number)[] = [1, 2, "three"];Pitfall 3:
thiscontext in callbacks
class Handler { handleClick() { // Wrong: this is lost button.addEventListener("click", this.handleClick); // Right: bind or use arrow function button.addEventListener("click", this.handleClick.bind(this)); // Or: button.addEventListener("click", () => this.handleClick()); }}Pitfall 4: Optional chaining doesnβt narrow types
function process(user?: { name: string }) { if (user?.name) { // user is still User | undefined here console.log(user.name.toUpperCase()); // β Error: Object is possibly 'undefined' // Fix: if (user && user.name) { ... } }}Pitfall 5: Generic constraints donβt work with
instanceof
function process<T extends Date | string>(value: T) { if (value instanceof Date) { // TypeScript doesn't narrow T here // Use type guard function instead }}Common Pitfalls
Type Narrowing Issues π―
// Issue: Type guards don't narrow in all contextsfunction isString(value: unknown): value is string { return typeof value === "string";}
function process(value: unknown) { if (isString(value)) { // β
Works: value is string }
// β Doesn't work: value is still unknown const result = isString(value) ? value.toUpperCase() : "default"; // Fix: Use if/else or assert}Generic Constraints π
// Issue: Generic constraints are checked at compile time, not runtimefunction process<T extends string>(value: T): T { return value.toUpperCase() as T; // Type assertion needed // Runtime: T is erased, only value exists}
// Better: Use type guards for runtime checksfunction process(value: string): string { return value.toUpperCase();}Module Augmentation π¦
// Issue: Augmenting modules requires proper declaration mergingdeclare module "some-module" { export interface Config { customOption?: string; }}
// Global augmentationdeclare global { interface Window { customProperty: string; }}