Skip to main content

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

TypeScript Version: This cheatsheet targets TypeScript 5.0+. Some features require specific versions (noted inline).

Terminal window
# Install TypeScript globally
pnpm add -g typescript
# Check TypeScript version
tsc --version
# β†’ Version 5.3.3
# Install TypeScript in project
pnpm add -D typescript
# Initialize TypeScript config
tsc --init

Node.js: TypeScript 5.0+ requires Node.js 14.17+ or Node.js 16+ for optimal performance.


Basic Types

Primitive Types πŸ”’

// Boolean
let 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;
// String
let name: string = "John";
let message: string = `Hello, ${name}!`; // Template literals
// Null and Undefined
let nullValue: null = null;
let undefinedValue: undefined = undefined;
// Symbol (ES6)
let sym: symbol = Symbol("key");

Array Types πŸ“‹

// Array syntax
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alice", "Bob"];
// Generic array syntax
let items: Array<number> = [1, 2, 3];
let users: Array<string> = ["Alice", "Bob"];
// Readonly arrays
let 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"; // βœ… Valid
tuple[1] = 20; // βœ… Valid
tuple[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]; // βœ… Valid

Object Types πŸ—οΈ

// Object type annotation
let user: { name: string; age: number } = {
name: "John",
age: 30,
};
// Optional properties
let config: { apiKey?: string; timeout: number } = {
timeout: 5000,
};
// Readonly properties
let 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 signatures
let dictionary: { [key: string]: number } = {
apples: 5,
oranges: 10,
};

Union & Intersection Types πŸ”€

// Union type (either/or)
let value: string | number = "hello";
value = 42; // βœ… Valid
value = true; // ❌ Error: Type 'boolean' is not assignable
// Union with null/undefined
let 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" }; // βœ… Valid

Literal Types πŸ“

// String literal types
type Direction = "north" | "south" | "east" | "west";
let dir: Direction = "north"; // βœ… Valid
dir = "up"; // ❌ Error: Type '"up"' is not assignable
// Numeric literal types
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 4; // βœ… Valid
// Boolean literal types
type Success = true;
let success: Success = true; // βœ… Valid
success = false; // ❌ Error: Type 'false' is not assignable

Enum Types 🎯

// Numeric enum (default)
enum Status {
Pending, // 0
Approved, // 1
Rejected, // 2
}
let status: Status = Status.Approved; // β†’ 1
// Numeric enum with custom values
enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500,
}
// String enum
enum 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 annotation
let count: number = 10;
// Type inference (preferred when type is obvious)
let name = "John"; // β†’ type: string
let age = 30; // β†’ type: number
// Type assertion (tell TypeScript you know better)
let value: unknown = "hello";
let str1: string = value as string; // Angle bracket syntax
let 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 property

Type Aliases 🏷️

// Basic type alias
type ID = string | number;
let userId: ID = "123";
let productId: ID = 456;
// Object type alias
type User = {
id: number;
name: string;
email?: string; // Optional property
};
// Union type alias
type Status = "pending" | "approved" | "rejected";
// Intersection type alias
type Admin = User & { permissions: string[] };
// Generic type alias
type Container<T> = { value: T };
let numberContainer: Container<number> = { value: 42 };

Functions

Function Declarations πŸ“€

// Function with type annotations
function add(a: number, b: number): number {
return a + b;
}
// Optional parameters
function greet(name: string, title?: string): string {
return title ? `${title} ${name}` : name;
}
greet("John"); // βœ… Valid
greet("John", "Dr."); // βœ… Valid
// Default parameters
function multiply(a: number, b: number = 1): number {
return a * b;
}
multiply(5); // β†’ 5 (b defaults to 1)
multiply(5, 3); // β†’ 15
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3); // β†’ 6
// Function overloads
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
return String(value);
}
format("hello"); // βœ… Valid
format(42); // βœ… Valid

Arrow Functions ➑️

// Arrow function with types
const add = (a: number, b: number): number => a + b;
// Arrow function with block body
const multiply = (a: number, b: number): number => {
return a * b;
};
// Arrow function type annotation
type MathOperation = (a: number, b: number) => number;
const subtract: MathOperation = (a, b) => a - b;

Function Types πŸ”§

// Function type syntax
type Handler = (event: string) => void;
const onClick: Handler = (event) => {
console.log(event);
};
// Function interface
interface Calculator {
(a: number, b: number): number;
}
const add: Calculator = (a, b) => a + b;
// Method signature in interface
interface UserService {
getUser(id: number): Promise<User>;
updateUser(id: number, data: Partial<User>): Promise<void>;
}
// Callable object
interface Callable {
(): string;
description: string;
}
const func: Callable = (() => {
const fn = () => "Hello";
fn.description = "Greeting function";
return fn;
})();

This Parameter 🎯

// Explicit this parameter
function handler(this: { name: string }, message: string) {
console.log(`${this.name}: ${message}`);
}
const context = { name: "App" };
handler.call(context, "Hello"); // βœ… Valid
handler("Hello"); // ❌ Error: The 'this' context is not assignable
// Arrow functions preserve this from outer scope
class 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 class
class 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 modifier
class 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 property

Static Members ⚑

class MathUtils {
static PI = 3.14159;
static add(a: number, b: number): number {
return a + b;
}
}
// Access static members without instance
MathUtils.PI; // β†’ 3.14159
MathUtils.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 class
class Animal {
constructor(public name: string) {}
move(distance: number = 0): void {
console.log(`${this.name} moved ${distance}m`);
}
}
// Derived class
class 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 setter
console.log(temp.fahrenheit); // β†’ 77 (uses getter)

Interfaces & Types

Interface Basics πŸ“‹

// Basic interface
interface User {
id: number;
name: string;
email: string;
}
// Optional properties
interface Config {
apiKey: string;
timeout?: number; // Optional
retries?: number; // Optional
}
// Readonly properties
interface Point {
readonly x: number;
readonly y: number;
}
// Index signatures
interface Dictionary {
[key: string]: number;
}
const scores: Dictionary = {
alice: 95,
bob: 87,
};

Interface Extends & Merging πŸ”—

// Interface extension
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// Multiple extension
interface 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 anotherProperty

Type vs Interface πŸ€”

// Interface - extensible, can be merged
interface User {
name: string;
}
interface User {
age: number; // Merges with previous declaration
}
// Type - can use unions, intersections, primitives
type Status = "pending" | "approved" | "rejected";
type ID = string | number;
type UserType = {
name: string;
age: number;
};
// Type cannot be merged
type 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 function
function identity<T>(arg: T): T {
return arg;
}
const str = identity<string>("hello"); // β†’ "hello"
const num = identity<number>(42); // β†’ 42
const inferred = identity("world"); // Type inferred as string
// Generic with constraints
function 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 parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair("hello", 42); // β†’ ["hello", 42]

Generic Classes πŸ—οΈ

// Generic class
class 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 extends
interface 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 keyof
function 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 parameters
interface 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 type
type IsArray<T> = T extends any[] ? true : false;
type Test1 = IsArray<string[]>; // β†’ true
type Test2 = IsArray<string>; // β†’ false
// Infer keyword
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Element = ArrayElement<string[]>; // β†’ string
// Extract return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
type StrArrOrNumArr = ToArray<string | number>; // β†’ string[] | number[]

Advanced Types

Mapped Types πŸ”„

// Make all properties optional
type Partial<T> = {
[P in keyof T]?: T[P];
};
// Make all properties readonly
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// Make all properties required
type Required<T> = {
[P in keyof T]-?: T[P];
};
// Pick specific properties
type 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 type
type 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 type
type EventName = "click" | "hover";
type HandlerName = `on${Capitalize<EventName>}`;
// β†’ "onClick" | "onHover"
// String manipulation types
type Uppercase<S extends string> = // Built-in
type Lowercase<S extends string> = // Built-in
type Capitalize<S extends string> = // Built-in
type Uncapitalize<S extends string> = // Built-in
// Pattern matching with infer
type 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 definition
type JsonValue =
| string
| number
| boolean
| null
| JsonValue[]
| { [key: string]: JsonValue };
// Recursive type alias
type 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 function
function 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 types
interface 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 function
function 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 condition
function assert(condition: unknown): asserts condition {
if (!condition) {
throw new Error("Assertion failed");
}
}

Narrowing Patterns 🎯

// typeof narrowing
function 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 narrowing
function 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 narrowing
interface 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 unions
type 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 declarations
export const PI = 3.14159;
export function add(a: number, b: number): number {
return a + b;
}
export class Calculator {
// ...
}
// Named exports
export { add, Calculator };
export { add as sum, Calculator as Calc };
// Default export
export default function greet(name: string): string {
return `Hello, ${name}`;
}
// Re-export
export { 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 imports
import { add, subtract } from "./math";
import { add as sum } from "./math";
// Default import
import greet from "./greet";
// Mixed imports
import greet, { add, subtract } from "./utils";
// Namespace import
import * 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 import
import "./polyfills"; // Executes file, no imports
// Dynamic import
const module = await import("./utils");
module.add(1, 2);

Module Resolution πŸ”

// Relative imports
import { utils } from "./utils";
import { config } from "../config";
// Absolute imports (configured in tsconfig.json)
import { api } from "@/api";
import { components } from "~/components";
// Node modules
import express from "express";
import { useState } from "react";
// Path mapping example (tsconfig.json)
// {
// "compilerOptions": {
// "baseUrl": ".",
// "paths": {
// "@/*": ["src/*"],
// "~/*": ["src/components/*"]
// }
// }
// }

Decorators

Class Decorators 🎨

// Basic class decorator
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
// Decorator factory
function color(value: string) {
return function (constructor: Function) {
// Add metadata or modify constructor
};
}
@color("blue")
class Car {
// ...
}

Method Decorators πŸ”§

// Method decorator
function enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
descriptor.enumerable = value;
};
}
class Greeter {
@enumerable(false)
greet() {
return "Hello";
}
}
// Property decorator
function format(target: any, propertyKey: string) {
// Modify property metadata
}
class User {
@format
name: string;
}

⚠️ Note: Decorators are experimental and require "experimentalDecorators": true in tsconfig.json. They may change in future TypeScript versions.


Utility Types

Common Utility Types πŸ› οΈ

// Partial<T> - Make all properties optional
type PartialUser = Partial<User>;
// Required<T> - Make all properties required
type RequiredUser = Required<User>;
// Readonly<T> - Make all properties readonly
type ReadonlyUser = Readonly<User>;
// Pick<T, K> - Select specific properties
type UserName = Pick<User, "name" | "email">;
// Omit<T, K> - Remove specific properties
type UserWithoutId = Omit<User, "id">;
// Record<K, T> - Create object type with keys K and values T
type StatusConfig = Record<"pending" | "approved", { color: string }>;
// Exclude<T, U> - Exclude types from union
type NonNull = Exclude<string | number | null, null>; // β†’ string | number
// Extract<T, U> - Extract types from union
type Numbers = Extract<string | number | boolean, number>; // β†’ number
// NonNullable<T> - Remove null and undefined
type DefiniteString = NonNullable<string | null | undefined>; // β†’ string
// Parameters<T> - Extract function parameters
type Params = Parameters<(a: string, b: number) => void>; // β†’ [string, number]
// ReturnType<T> - Extract return type
type 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 of const name: string = "John"
  • Prefer type over interface for unions, intersections, and primitives
  • Use interface for 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 unknown instead of any when type is truly unknown
  • Enable noImplicitAny to 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 - use unknown and 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 interface and type arbitrarily - follow consistent patterns
  • Don’t use @ts-ignore - fix the underlying type issue instead
  • Don’t use Function type - use specific function signatures
  • Don’t use object type - use Record<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-error without 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: this context 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 contexts
function 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 runtime
function 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 checks
function process(value: string): string {
return value.toUpperCase();
}

Module Augmentation πŸ“¦

// Issue: Augmenting modules requires proper declaration merging
declare module "some-module" {
export interface Config {
customOption?: string;
}
}
// Global augmentation
declare global {
interface Window {
customProperty: string;
}
}