TypeScript Cheat Sheet
Every type, every pattern, searchable and filterable by difficulty.
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
let n: null = null;
let u: undefined = undefined;
let y: unknown = 42; // safer than any
function log(): void { console.log("hi"); }
function fail(): never { throw new Error("!"); }
const config = { host: "localhost", port: 3000 } as const;
// port → 3000 (literal), not number
const dirs = ["up", "down"] as const;
type Dir = typeof dirs[number]; // "up" | "down"
type Colors = Record<string, string | number[]>;
const palette = {
red: [255, 0, 0],
green: "#00ff00",
} satisfies Colors;
palette.red.map(x => x); // still number[]
palette.green.toUpperCase(); // still string
let id: string | number;
type Status = "active" | "inactive" | "pending";
type Admin = User & Permissions; // all props of both
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number }
| { kind: "triangle"; base: number; h: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "rect": return s.width * s.height;
case "triangle": return 0.5 * s.base * s.h;
}
}
function pad(x: string | number) {
if (typeof x === "string") return x.toUpperCase();
return x * 2;
}
if (err instanceof Error) console.log(err.message);
function isUser(x: unknown): x is User {
return typeof x === "object" && x !== null && "id" in x;
}
function assert(cond: boolean, msg: string): asserts cond {
if (!cond) throw new Error(msg);
}
type Point = { x: number; y: number };
type ID = string | number;
enum Color { Red = "RED", Green = "GREEN", Blue = "BLUE" }
const enum Dir { Up, Down, Left, Right } // inlined at compile time
| Feature | interface | type |
|---|---|---|
| Object shape | yes | yes |
| Union / intersection | no | yes |
| Primitive alias | no | yes |
| Tuple | no | yes |
| Mapped / conditional types | no | yes |
| extends keyword | yes | via & |
| Declaration merging | yes | no |
| implements in class | yes | yes |
| Error messages | cleaner | sometimes verbose |
interface User {
id: number;
name: string;
email?: string; // optional
readonly createdAt: Date; // immutable
}
interface Repo {
find(id: number): User | undefined;
save(user: User): void;
[key: string]: unknown;
}
interface Dog extends Animal { breed: string }
interface AdminUser extends User, Permissions { role: "admin" }
interface Window { myLib: MyLibrary }
// In another file:
interface Window { analytics: Analytics }
// Both merge automatically. type aliases cannot do this.
function add(a: number, b: number): number { return a + b; }
const mul = (x: number, y: number): number => x * y;
function greet(name: string, prefix = "Hi", title?: string) {
return `${prefix}, ${title ?? ""}${name}`;
}
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
type Handler = (event: MouseEvent) => void;
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/users/${id}`);
return res.json();
}
async function load(): Promise<User | null> {
try { return await fetchUser(1); }
catch (err: unknown) {
if (err instanceof Error) console.error(err.message);
return null;
}
}
const [user, posts] = await Promise.all([fetchUser(1), fetchPosts(1)]);
function format(x: string): string;
function format(x: number): string;
function format(x: any): string { return String(x); }
function onClick(this: HTMLButtonElement) {
console.log(this.textContent);
}
class Person {
static readonly species = "Homo sapiens";
constructor(
public name: string,
private _age: number,
protected role: string = "user",
) {}
get age() { return this._age; }
set age(v: number) {
if (v < 0) throw new Error("negative");
this._age = v;
}
}
abstract class Shape {
abstract area(): number;
describe() { return `area: ${this.area()}`; }
}
class Square extends Shape {
constructor(private s: number) { super(); }
area() { return this.s ** 2; }
}
interface Serializable { serialize(): string }
class Model implements Serializable {
serialize() { return JSON.stringify(this); }
}
function identity<T>(value: T): T { return value; }
interface ApiResponse<T> { data: T; status: number }
function getLength<T extends { length: number }>(x: T) { return x.length; }
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
}
interface Container<T = string> { value: T }
type A = Partial<User>; // all props optional
type B = Required<User>; // removes all ?
type C = Readonly<User>; // all props readonly
// Pick: allowlist - only named keys survive
type Preview = Pick<User, "id" | "name">;
// Omit: blocklist - all keys except named ones
type Public = Omit<User, "password">;
type Scores = Record<string, number>;
type T1 = Exclude<"a"|"b"|"c", "b">; // "a"|"c"
type T2 = Extract<"a"|"b", "b"|"c">; // "b"
type T3 = NonNullable<string|null>; // string
type R = ReturnType<typeof fetchUser>;
type P = Parameters<typeof fetchUser>;
type AW = Awaited<Promise<string>>; // string
type Optional<T> = { [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]
};
type IsArray<T> = T extends any[] ? "yes" : "no";
type A = IsArray<number[]>; // "yes"
type B = IsArray<string>; // "no"
type Unpack<T> =
T extends Promise<infer U> ? U : T;
type R = Unpack<Promise<string>>; // string
type ElementType<T> =
T extends (infer E)[] ? E : never;
type N = ElementType<number[]>; // number
type S = ElementType<string[][]>; // string[]
// With an upper bound constraint:
type NumericEl<T> =
T extends (infer E extends number)[] ? E : never;
type X = NumericEl<1[] | 2[]>; // 1 | 2
type Ev = "click" | "focus" | "blur";
type Handler = `on${Capitalize<Ev>}`;
// "onClick" | "onFocus" | "onBlur"
type U = Uppercase<"hello">; // "HELLO"
namespace Validation {
export interface Validator { isValid(s: string): boolean }
export class Email implements Validator {
isValid(s: string) { return s.includes("@"); }
}
}
type Brand<T, B> = T & { readonly _brand: B };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
function makeUserId(id: string): UserId {
return id as UserId;
}
function getUser(id: UserId) { /* ... */ }
const uid = makeUserId("u-123");
const oid = "o-456" as OrderId;
getUser(uid); // ok
// getUser(oid); // Error: OrderId is not assignable to UserId
class QueryBuilder {
private conditions: string[] = [];
private _limit = 100;
private _table = "";
from(table: string): this {
this._table = table; return this;
}
where(cond: string): this {
this.conditions.push(cond); return this;
}
limit(n: number): this {
this._limit = n; return this;
}
build(): string {
const where = this.conditions.join(" AND ");
return `SELECT * FROM ${this._table} WHERE ${where} LIMIT ${this._limit}`;
}
}
const sql = new QueryBuilder()
.from("users")
.where("active = true")
.limit(10)
.build();
type BuilderState = { name?: string; age?: number };
function createUser() {
const state: BuilderState = {};
return {
name(n: string) { state.name = n; return this; },
age(a: number) { state.age = a; return this; },
build() {
if (!state.name) throw new Error("name required");
return state as Required<BuilderState>;
},
};
}
const user = createUser().name("Alice").age(30).build();
// tsconfig: "noUncheckedIndexedAccess": true
const arr: number[] = [1, 2, 3];
// WITHOUT the flag:
const a = arr[0]; // type: number
// WITH the flag:
const b = arr[0]; // type: number | undefined
// b.toFixed(2); // Error: b might be undefined
// Fix 1: optional chaining
b?.toFixed(2);
// Fix 2: explicit check
if (b !== undefined) b.toFixed(2);
// Same applies to Record index:
const map: Record<string, number> = {};
const c = map["key"]; // number | undefined
function Sealed(target: Function) {
Object.seal(target); Object.seal(target.prototype);
}
@Sealed
class BugReport { title = "report"; }
function Log(target: any, key: string, desc: PropertyDescriptor) {
const orig = desc.value;
desc.value = function(...args: any[]) {
console.log(`${key} called with`, args);
return orig.apply(this, args);
};
}
class Service {
@Log getData(id: number) { /* ... */ }
}
import { FC } from "react";
type ButtonProps = {
label: string;
onClick: () => void;
variant?: "primary" | "ghost";
disabled?: boolean;
};
const Button: FC<ButtonProps> = ({
label, onClick, variant = "primary", disabled = false
}) => (
<button onClick={onClick} disabled={disabled}
className={variant}>
{label}
</button>
);
import { ReactNode } from "react";
type CardProps = {
title: string;
children: ReactNode;
};
function Card({ title, children }: CardProps) {
return <div><h2>{title}</h2>{children}</div>;
}
import { ButtonHTMLAttributes } from "react";
type MyButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
loading?: boolean;
};
function MyButton({ loading, children, ...rest }: MyButtonProps) {
return <button {...rest}>{loading ? "..." : children}</button>;
}
import { useState } from "react";
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
import { useRef } from "react";
// DOM ref: pass null as initial value
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
// Mutable ref: no null initial value
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
timerRef.current = setTimeout(() => {}, 1000);
type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "reset"; payload: number };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment": return state + 1;
case "decrement": return state - 1;
case "reset": return action.payload;
}
}
const [count, dispatch] = useReducer(reducer, 0);
import {
ChangeEvent, MouseEvent, FormEvent,
KeyboardEvent, FocusEvent,
} from "react";
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
};
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
type ListProps<T> = {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
};
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
import { PI, square } from "./math";
import User from "./user"; // default
import * as Utils from "./utils"; // namespace
import type { User, Post } from "./types";
// Fully erased at runtime
// global.d.ts
declare module "*.svg" {
const src: string;
export default src;
}
declare global {
interface Window { myApp: AppInstance }
}
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"outDir": "./dist",
"sourceMap": true,
"declaration": true
}
}
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"]
}
}
}
TypeScript cheat sheet for developers who want fast, reliable reference without digging through documentation.
Covers everything from primitives and type aliases to advanced patterns like mapped types, conditional types, branded types, and the builder pattern. Every snippet is syntax-highlighted, copy-ready, and tagged by difficulty so beginners and experienced engineers can both find what they need quickly.
What's inside:
-
Types & Basics - primitives,
as const,satisfies, discriminated unions, type guards -
Interfaces - definition, extending, declaration merging, plus a full
interfacevstypecomparison table -
Functions - typed functions, async/await, overloads,
thisparameter -
Classes - constructor shorthand, abstract classes, getters/setters,
implements -
Generics - constraints,
keyof, generic classes, default type parameters -
Utility Types -
Partial,Pick,Omit,Record,ReturnType,Awaited, and more -
Advanced - mapped types, conditional types,
inferwith constraints, template literal types, namespaces -
Patterns - branded/opaque types, builder pattern,
noUncheckedIndexedAccessexplained with real before/after examples -
Decorators - class, method, and property decorators for TS 5+
-
React + TypeScript - props typing,
FC,ReactNode,useRef,useReducer, all common event types, generic components -
Modules & Config - imports,
import type, ambient modules, recommendedtsconfig.jsonoptions
Built-in difficulty filter (Basic / Intermediate / Advanced) lets you hide noise or focus on what you're learning. Search across all tabs instantly. Preview boxes explain what each type resolves to, not just what the syntax looks like.
Compatible with TypeScript 5.x, with version badges marking features introduced in TS 4+ and TS 5+.
What is TypeScript
TypeScript is a statically typed superset of JavaScript, developed and maintained by Microsoft.
It compiles down to plain JavaScript, which means it runs anywhere JavaScript runs - browsers, Node.js, Deno, Bun.
The key difference from JavaScript is the type system. TypeScript catches type errors at compile time, not at runtime, which prevents entire categories of bugs before your code ever runs.
It was created by Anders Hejlsberg, the same person behind C#. First released in 2012, it has since become one of the most widely used languages in front-end development and back-end development alike.
TypeScript doesn't replace JavaScript. It extends it. Every valid JavaScript file is also a valid TypeScript file.
TypeScript Basic Types
Primitive Types in TypeScript
TypeScript has seven primitive types inherited directly from JavaScript: string, number, boolean, null, undefined, symbol, and bigint.
let username: string = "Alice";
let age: number = 30;
let isActive: boolean = true;
let bigNum: bigint = 9007199254740991n;
null and undefined behave as distinct types when strict mode is enabled in tsconfig.json.
Special Types in TypeScript
Four types sit outside the normal type hierarchy and handle edge cases.
|
Type |
What it does |
|---|---|
|
|
Disables type checking entirely. Avoid when possible. |
|
|
Like |
|
|
Return type for functions that return nothing. |
|
|
A value that never occurs - exhaustive checks, infinite loops, thrown errors. |
unknown vs any is one of those distinctions that trips people up early on.
Use unknown when you receive data from an external source (API response, user input). Use any only as a last resort or during migration from JavaScript.
Type Inference in TypeScript
TypeScript infers types automatically when you assign a value at declaration.
let count = 0; // inferred as number
let name = "Bob"; // inferred as string
You don't need to annotate every variable. The type inference engine handles most cases correctly.
Explicit annotations make sense for function signatures, complex objects, and anywhere the inferred type would be too broad.
TypeScript Type Annotations
Variable Type Annotation Syntax
Add a colon after the variable name, followed by the type.
let score: number;
let label: string = "draft";
let flags: boolean[] = [true, false];
Annotations separate declaration from assignment cleanly. Useful when you want to declare a variable before initializing it.
Function Parameter and Return Type Annotations
Annotate parameters directly in the function signature. The return type goes after the closing parenthesis.
function greet(name: string): string {
return `Hello, ${name}`;
}
function logError(message: string): void {
console.error(message);
}
Explicit return types are a good habit. They catch cases where a function accidentally returns undefined on one branch.
Object Type Annotations
Define the shape of an object inline or via a type alias.
let user: { name: string; age: number; isAdmin: boolean };
user = { name: "Alice", age: 28, isAdmin: false };
Inline object annotations work fine for one-off cases. For anything reused across your codebase, use an interface or type alias instead.
TypeScript Arrays and Tuples
Array Type Syntax in TypeScript
Two valid syntaxes. Both are equivalent.
let scores: number[] = [10, 20, 30];
let names: Array<string> = ["Alice", "Bob"];
number[] is more common in practice. Array<string> shows up more often in generic contexts.
Arrays in TypeScript are still standard JavaScript arrays under the hood - static typing just enforces what goes inside them.
Tuple Types in TypeScript
Tuples are fixed-length arrays where each position has a specific type.
let point: [number, number] = [42, 87];
let entry: [string, number] = ["Alice", 28];
Useful for returning multiple values from a function without creating a full object. React's useState hook returns a tuple, for example - [value, setter].
Optional tuple elements use ?. Rest elements work too, at the end of the tuple.
let flexible: [string, number?] = ["hello"];
TypeScript Interfaces
How to Define an Interface in TypeScript
An interface describes the shape of an object. It's a contract - any object assigned to that interface must match its structure.
interface User {
name: string;
age: number;
email: string;
}
Interfaces are one of the first things you'll use heavily in any real TypeScript project.
They show up constantly in API integration work, where you need to type-check incoming response data against a defined shape.
Optional and Readonly Properties in Interfaces
Mark optional properties with ?. Mark immutable properties with readonly.
interface Product {
readonly id: number;
name: string;
description?: string;
}
readonly prevents reassignment after the object is created. ? means the property may or may not exist on the object.
Both are common in real-world TypeScript - especially when modeling API responses where some fields aren't always present.
Interface Extension in TypeScript
Interfaces can extend one or more other interfaces using the extends keyword.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
This keeps your type definitions DRY. Instead of redefining shared properties, you build on top of a base interface.
Multiple extension works too: interface C extends A, B {}.
Difference Between Interface and Type in TypeScript
Both describe object shapes. The differences are mostly subtle.
|
|
|
|
|---|---|---|
|
Extension |
|
|
|
Declaration merging |
Yes |
No |
|
Union types |
No |
Yes |
|
Primitive aliases |
No |
Yes |
Use interface for object shapes and class contracts. Use type for union types, intersection types, and type aliases on primitives or complex combinations.
In practice, a lot of teams just pick one and stick with it.
TypeScript Type Aliases
How to Create a Type Alias
Use the type keyword to assign a name to any type expression.
type UserID = string;
type Coordinates = { x: number; y: number };
Type aliases work for primitives, objects, unions, intersections, and function signatures - more flexible than interfaces in that regard.
Union Types in TypeScript
A union type allows a value to be one of several types, separated by |.
type Status = "active" | "inactive" | "pending";
type ID = string | number;
String literal unions are especially useful for constraining function arguments to a fixed set of values.
Intersection Types in TypeScript
Intersection types combine multiple types into one using &. The result must satisfy all of them.
type Admin = User & { permissions: string[] };
Good for composing object types without full interface extension. Used heavily in utility-type patterns and generic constraints.
TypeScript Enums
Numeric Enums
Numeric enums auto-increment from 0 by default. You can set a custom starting value.
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
String Enums
Each member must have an explicit string value. No auto-incrementing.
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
String enums are easier to debug - the values are readable in logs and network responses, unlike numeric enums.
Const Enums in TypeScript
const enum gets inlined at compile time. No enum object is emitted in the output JavaScript.
const enum Axis { X, Y, Z }
let a = Axis.X; // compiles to: let a = 0
Smaller output, faster execution. The tradeoff: no reverse mapping, and they don't work in all module systems.
TypeScript Functions
Function Type Signatures in TypeScript
Define the full type signature of a function using a type alias or inline annotation.
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => a + b;
Optional and Default Parameters
Optional parameters use ?; default parameters use =. Optional must come after required.
function greet(name: string, greeting?: string): string {
return `${greeting ?? "Hello"}, ${name}`;
}
function power(base: number, exp: number = 2): number {
return base ** exp;
}
Rest Parameters in TypeScript
Rest parameters collect remaining arguments into a typed array.
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
Function Overloads in TypeScript
Define multiple signatures for the same function, then implement with a general signature.
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
return String(value);
}
Overloads improve type safety at call sites without duplicating logic. Useful when a function behaves differently depending on input type.
TypeScript Classes
Class Syntax in TypeScript
TypeScript classes extend JavaScript ES6 classes with type annotations, access modifiers, and strict property initialization.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
Access Modifiers: public, private, protected
|
Modifier |
Accessible from |
|---|---|
|
|
Anywhere (default) |
|
|
Only within the class |
|
|
Class and subclasses |
TypeScript also supports the native # JavaScript private field syntax for runtime-enforced privacy.
Readonly and Static Members
readonly properties can only be set in the constructor. static members belong to the class itself, not instances.
class Config {
static readonly MAX_RETRIES = 3;
readonly id: string;
constructor(id: string) {
this.id = id;
}
}
Abstract Classes in TypeScript
Abstract classes can't be instantiated directly. They define a base structure that subclasses must implement.
abstract class Shape {
abstract area(): number;
}
class Circle extends Shape {
constructor(private radius: number) { super(); }
area(): number { return Math.PI * this.radius ** 2; }
}
Class Implementing an Interface
A class uses implements to declare it satisfies an interface contract. TypeScript enforces it at compile time.
interface Printable {
print(): void;
}
class Report implements Printable {
print() { console.log("Printing..."); }
}
TypeScript Generics
Generic Functions in TypeScript
Generics let you write functions that work with any type while preserving type safety.
function identity<T>(value: T): T {
return value;
}
identity<string>("hello"); // T = string
identity(42); // T inferred as number
Generic Interfaces and Classes
Apply generics to interfaces and classes the same way - declare the type parameter after the name.
interface Box<T> {
value: T;
}
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
}
Generic Constraints in TypeScript
Use extends to constrain what types a generic can accept.
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
Works on strings, arrays, or any object with a length property. Rejects types that don't match.
Default Generic Types
Generics can have defaults, used when no type argument is passed.
interface Response<T = unknown> {
data: T;
status: number;
}
TypeScript Utility Types
Partial, Required, and Readonly
|
Utility |
What it does |
|---|---|
|
|
Makes all properties optional |
|
|
Makes all properties required |
|
|
Prevents mutation of all properties |
type DraftUser = Partial<User>;
type FrozenUser = Readonly<User>;
Pick, Omit, and Exclude
Pick<T, K> selects a subset of properties; Omit<T, K> removes them; Exclude<T, U> filters union members.
type Preview = Pick<User, "name" | "email">;
type WithoutEmail = Omit<User, "email">;
type NonString = Exclude<string | number | boolean, string>;
Record Type in TypeScript
Record<K, V> creates an object type with keys of type K and values of type V.
type Scores = Record<string, number>;
const results: Scores = { alice: 95, bob: 87 };
Clean alternative to index signatures for fixed-value maps.
ReturnType and Parameters Utility Types
ReturnType<T> extracts the return type of a function. Parameters<T> extracts its parameter types as a tuple.
function fetchUser(): { id: number; name: string } { ... }
type UserResult = ReturnType<typeof fetchUser>; // { id: number; name: string }
type FetchArgs = Parameters<typeof fetchUser>; // []
Useful when you need to reuse types from existing functions without duplicating the annotations.
TypeScript Type Narrowing
typeof and instanceof Guards
typeof narrows primitive types; instanceof narrows class instances.
function process(val: string | number) {
if (typeof val === "string") return val.toUpperCase();
return val.toFixed(2);
}
in Operator for Type Narrowing
The in operator checks if a property exists on an object, narrowing to the type that contains it.
if ("email" in user) {
console.log(user.email);
}
Discriminated Unions
A discriminated union uses a shared literal property to distinguish between union members. TypeScript narrows based on it automatically.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(s: Shape) {
if (s.kind === "circle") return Math.PI * s.radius ** 2;
return s.side ** 2;
}
Type Assertion in TypeScript
Type assertions tell TypeScript to treat a value as a specific type. Two syntaxes - prefer as in TSX files.
const input = document.getElementById("name") as HTMLInputElement;
Assertions bypass type checking. Use only when you're certain about the type - wrong assertions cause runtime bugs.
TypeScript Modules
Import and Export Syntax in TypeScript
TypeScript uses ES module syntax. Named exports allow multiple exports per file; default exports allow one.
// math.ts
export function add(a: number, b: number): number { return a + b; }
export const PI = 3.14159;
// main.ts
import { add, PI } from "./math";
Default Exports vs Named Exports
Default exports: one per file, imported with any name. Named exports: multiple per file, imported by exact name (or aliased with as).
Most style guides and linting rules prefer named exports - easier to refactor and better for linting in programming tools like ESLint.
Module Resolution in TypeScript
TypeScript resolves modules using either node or bundler strategy (set in tsconfig.json).
moduleResolution: "bundler" is the recommended setting for modern projects using Vite, esbuild, or similar tools.
Path aliases (@/components/*) require both tsconfig.json paths and bundler configuration to work correctly.
TypeScript Decorators
Class Decorators
A class decorator is a function applied to a constructor. Prefixed with @.
function sealed(constructor: Function) {
Object.seal(constructor);
}
@sealed
class BankAccount { ... }
Decorators require "experimentalDecorators": true in tsconfig.json. The TC39 Stage 3 decorator proposal uses a different syntax - check which version your toolchain supports.
Method and Property Decorators
Method decorators receive the target object, method name, and property descriptor. Property decorators receive only the target and name.
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${key}`);
return original.apply(this, args);
};
}
Used heavily in frameworks like NestJS and Angular for routing, validation, and dependency injection.
Parameter Decorators
Applied to individual function parameters. Receive the target, method name, and parameter index.
function Inject(token: string) {
return (target: any, key: string, index: number) => {
// register injection metadata
};
}
Rarely used outside of framework internals. NestJS uses them for its Dependency Injection system.
TypeScript Configuration
tsconfig.json File Structure
tsconfig.json sits at the project root and controls compiler behavior. All TypeScript tooling reads from it automatically.
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Key Compiler Options in tsconfig.json
The most important options for day-to-day TypeScript work:
|
Option |
Purpose |
|---|---|
|
|
Output JavaScript version |
|
|
Module system (ESNext, CommonJS) |
|
|
Enables all strict checks |
|
|
Output directory for compiled files |
|
|
Root of source files |
|
|
Generates |
|
|
Type-check only, no output |
strict Mode Settings
"strict": true enables a bundle of checks: noImplicitAny, strictNullChecks, strictFunctionTypes, strictPropertyInitialization, and more.
Turn it on from the start. Retrofitting strict mode into a large, existing codebase is painful.
module and target Options
target controls the output syntax; module controls how imports/exports are handled.
For modern projects: "target": "ES2020" and "module": "ESNext". For Node.js: "module": "CommonJS" or "NodeNext" depending on your Node version.
Path Aliases in tsconfig.json
Path aliases shorten import paths and remove ../../.. chains.
"paths": {
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
Also configure the same aliases in your bundler (Vite, Webpack, etc.) - tsconfig paths only affect type checking, not module resolution at build time.
TypeScript with JavaScript Interoperability
Declaration Files (.d.ts) in TypeScript
.d.ts files describe the types of JavaScript code without containing any implementation. TypeScript uses them to type-check against untyped JS libraries.
They're generated automatically via "declaration": true in tsconfig, or written manually for custom JS modules.
@types Packages and DefinitelyTyped
DefinitelyTyped is the community repository for TypeScript declaration files. Install type definitions via @types/ packages on npm.
npm install --save-dev @types/node @types/lodash
If a library already ships its own types (check package.json for a "types" field), you don't need a @types package.
allowJs and checkJs Options
allowJs: true lets TypeScript process .js files alongside .ts files - useful during gradual migration.
checkJs: true enables type checking in JavaScript files using JSDoc annotations. Good for projects that can't fully commit to TypeScript yet.
TypeScript Compilation and Tooling
How to Compile TypeScript with tsc
tsc is the TypeScript compiler. Install it via npm, then run it from the project root.
npm install --save-dev typescript
npx tsc # compile using tsconfig.json
npx tsc --watch # watch mode
npx tsc --noEmit # type-check only
TypeScript with Webpack and Bundlers
Use ts-loader or babel-loader with @babel/preset-typescript to process TypeScript in Webpack.
Vite and esbuild handle TypeScript out of the box - no loader config needed. They strip types without checking them, so run tsc --noEmit separately in your CI pipeline or as part of your build pipeline.
TypeScript with Node.js
For Node.js projects, set "module": "CommonJS" or use "module": "NodeNext" with .mts/.cts extensions.
ts-node runs TypeScript directly in Node without a compile step - useful for scripts and development. For production, compile to JavaScript first.
TypeScript with React (TSX)
Enable JSX support with "jsx": "react-jsx" in tsconfig. Use .tsx extension for files containing JSX.
interface ButtonProps {
label: string;
onClick: () => void;
}
const Button = ({ label, onClick }: ButtonProps) => (
<button onClick={onClick}>{label}</button>
);
TypeScript and React work well together - typed props eliminate an entire class of component bugs that would otherwise surface at runtime in your web apps.
FAQ on TypeScript Cheat Sheets
What is the difference between TypeScript and JavaScript?
TypeScript is a statically typed superset of JavaScript that compiles to plain JS.
JavaScript has no type system; TypeScript adds type annotations, interfaces, and compile-time error checking. Every valid JavaScript file is also valid TypeScript.
Do I need to learn JavaScript before TypeScript?
Yes. TypeScript builds directly on JavaScript syntax and behavior.
Without a solid JavaScript foundation, TypeScript's type system won't make much sense. Learn JS fundamentals first, then layer on type annotations and interfaces.
What does strict mode do in TypeScript?
"strict": true in tsconfig.json enables a bundle of safety checks including strictNullChecks and noImplicitAny.
It prevents entire categories of bugs. Turn it on from day one - retrofitting it into an existing codebase is significantly harder.
What is type inference in TypeScript?
Type inference means TypeScript automatically determines a variable's type from its assigned value, without explicit annotation.
let count = 0 is inferred as number. You only need manual annotations for function signatures, complex objects, or ambiguous cases.
When should I use interface vs type in TypeScript?
Use interface for object shapes and class contracts. Use type for union types, intersections, and primitive aliases.
Both are largely interchangeable for object definitions. Most teams pick one convention and stay consistent throughout the codebase.
What are TypeScript utility types?
Built-in generic types that transform existing types. Partial<T> makes all properties optional; Pick<T, K> selects specific properties; Readonly<T> prevents mutation.
They remove repetitive type definitions and keep your code DRY without custom logic.
What is a .d.ts file in TypeScript?
A declaration file that describes the types of a JavaScript module without containing any implementation.
TypeScript uses .d.ts files to type-check against untyped JS libraries. They're auto-generated via "declaration": true in tsconfig.json or sourced from @types/ packages.
How do TypeScript generics work?
Generics let you write reusable functions and classes that work with any type while preserving type safety.
function identity<T>(val: T): T works for strings, numbers, or objects. The type is inferred from the argument or passed explicitly at the call site.
What is tsconfig.json and why does it matter?
tsconfig.json controls how the TypeScript compiler processes your project - output target, module system, strict checks, path aliases, and more.
Every TypeScript toolchain reads from it. Without it, the compiler falls back to defaults that rarely match real project needs.
Can TypeScript be used with React and Node.js?
Yes, widely. For React, enable "jsx": "react-jsx" in tsconfig and use .tsx files.
For back-end development with Node.js, set "module": "CommonJS" or "NodeNext". Both ecosystems have strong type definition support via DefinitelyTyped.