TypeScript
WebOverview
Typed superset of JavaScript that compiles to plain JS. Adds static types, interfaces, and powerful tooling to JavaScript.
Resources
Popular learning and reference links:
Installation & Getting Started
TypeScript requires a JavaScript runtime plus the TypeScript compiler. Some runtimes (Deno, Bun) support TypeScript natively.
# Install Node.js (see JavaScript) then add TypeScript
npm install -g typescript
tsc --version
# Or install locally per-project
npm install --save-dev typescript
npx tsc --version
# Deno — runs TypeScript natively, no setup
deno --version
deno run script.ts
# Bun — native TypeScript support
bun run script.ts
# tsx — run TS files directly in Node.js
npm install -g tsx
tsx script.ts
# REPL — no official TS REPL, but use tsx or ts-node
npx tsx # Interactive TypeScript REPL
npx ts-node # Alternative REPL
deno # Deno REPL supports TypeScript
# Compile and run
npx tsc script.ts && node script.js
# Type-check without emitting
npx tsc --noEmit Project Scaffolding
Initialize a tsconfig.json or use a framework CLI that includes TypeScript.
# Basic TypeScript project
mkdir my-project && cd my-project
npm init -y
npm install --save-dev typescript
npx tsc --init
# Using Vite with TypeScript
npm create vite@latest my-app -- --template vanilla-ts
# Using tsx for scripts (no build step)
npm install --save-dev tsx
npx tsx src/index.ts
# Run with ts-node
npx ts-node src/index.ts Package Management
Uses the same package managers as JavaScript. Type definitions are installed separately for untyped packages.
# Install a package
npm install zod
# Install type definitions
npm install --save-dev @types/node
# Most modern packages include types
npm install axios # types included
# Using pnpm
pnpm add zod
# Using bun (has built-in TS support)
bun add zod Tooling & Formatter/Linter
Same tools as JavaScript, with TypeScript-aware configurations. tsc itself is the primary type checker.
# Prettier — formats TS natively
npx prettier --write "**/*.ts"
# ESLint with TypeScript
npm install --save-dev eslint typescript-eslint
npx eslint .
# Biome — supports TypeScript out of the box
npx biome check --write .
# Type checking (not a linter, but essential)
npx tsc --noEmit
// eslint.config.js (flat config with TypeScript)
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
},
},
},
)
# Editor support
# VS Code: ESLint + Prettier extensions
# Biome: Biome VS Code extension
# Type errors shown inline via TypeScript language server Build & Compile Model
Transpiled to JavaScript, then interpreted/JIT. TypeScript is compiled to JS by tsc, or stripped/transpiled by faster tools.
# tsc — official compiler (type-check + emit JS)
npx tsc # Compile project
npx tsc --noEmit # Type-check only (no output)
npx tsc --watch # Watch mode
# Faster alternatives (strip types, no type-checking)
npx esbuild src/index.ts --bundle --outfile=out.js
npx tsup src/index.ts # Library bundler
npx tsx src/index.ts # Run directly (no build)
# Output: .js + .d.ts (type declarations) + .js.map
Execution model:
- TypeScript → JavaScript (types erased) → Engine JIT
tscperforms full type checking but is slow for large projects- Most build tools (esbuild, SWC, Bun) only strip types — no type checking
--isolatedDeclarationsenables parallel .d.ts generation- Node.js 22+ can run
.tsfiles directly (type stripping)
Libraries & Frameworks
TypeScript uses the same ecosystem as JavaScript. These libraries are TypeScript-first or have excellent type support:
Type-First Libraries - Zod (schema validation), tRPC (end-to-end typesafe APIs), Drizzle ORM, Effect, ts-pattern
Web Frameworks - Next.js, Nuxt, SvelteKit, Remix, Astro, Hono - all have first-class TS support
Full-Stack - T3 Stack (Next.js + tRPC + Prisma + Tailwind), Blitz.js, RedwoodJS
Runtime/Build - tsx, tsup, unbuild, Vite, esbuild
State Management - Zustand, Jotai, XState - all TypeScript-first
Validation - Zod, Valibot, ArkType, io-ts, Yup
API Clients - Axios, ky, openapi-typescript, GraphQL Code Generator
Type Utilities - type-fest, ts-essentials, utility-types
Testing - Vitest, Jest, Playwright - all TypeScript-native
Testing
Same testing tools as JavaScript — Vitest and Jest work natively with TypeScript. Type-level testing is also possible.
// Vitest with TypeScript (zero config)
import { describe, it, expect } from 'vitest'
function add(a: number, b: number): number {
return a + b
}
describe('add', () => {
it('adds two numbers', () => {
expect(add(1, 2)).toBe(3)
})
it('returns a number', () => {
const result: number = add(1, 2)
expect(typeof result).toBe('number')
})
})
// Type-level testing with expectTypeOf (Vitest)
import { expectTypeOf } from 'vitest'
expectTypeOf(add).toBeFunction()
expectTypeOf(add).parameter(0).toBeNumber()
expectTypeOf(add).returns.toBeNumber()
# Run tests
npx vitest # Vitest (watch mode)
npx jest # Jest (needs ts-jest or @swc/jest)
node --test # Node.js built-in (with tsx loader)
# Type-check as a test step
npx tsc --noEmit Debugging
Same tools as JavaScript. Source maps enable debugging TypeScript directly. VS Code has first-class TS debugging support.
# Source maps — map compiled JS back to TS
# tsconfig.json: "sourceMap": true
# VS Code debugging (recommended)
# launch.json:
{
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/index.ts",
"runtimeExecutable": "tsx",
"console": "integratedTerminal"
}
# Or with ts-node
{
"type": "node",
"request": "launch",
"runtimeArgs": ["-r", "ts-node/register"],
"args": ["${workspaceFolder}/src/index.ts"]
}
# Chrome DevTools — works with source maps
node --inspect -r tsx/cjs src/index.ts
// Type-narrowing aids debugging
function process(input: string | number) {
if (typeof input === 'string') {
// TypeScript knows input is string here
console.log(input.toUpperCase())
}
}
// satisfies — catch type errors without changing runtime
const config = {
port: 3000,
host: 'localhost',
} satisfies Record<string, string | number>
// // @ts-expect-error — document expected errors
// @ts-expect-error — testing invalid input
process(null) Variables
Same as JavaScript but with type annotations. Types can be explicit or inferred.
const name: string = 'Alice';
let age: number = 30;
// Type inference (preferred when obvious)
const inferred = 'hello'; // type: string
// Destructuring with types
const { x, y }: { x: number; y: number } = { x: 1, y: 2 };
const [first, ...rest]: number[] = [1, 2, 3];
// Constants with literal types
const direction = 'north' as const; // type: "north" Types
Rich type system with interfaces, unions, generics, and utility types.
// Primitives
let s: string = 'hello';
let n: number = 42;
let b: boolean = true;
// Interfaces
interface User {
name: string;
age: number;
email?: string; // optional
}
// Union types
type Status = 'active' | 'inactive' | 'pending';
// Generics
function identity<T>(value: T): T {
return value;
}
// Utility types
type Partial<T> = { [P in keyof T]?: T[P] };
type ReadonlyUser = Readonly<User>; Data Structures
Same runtime structures as JavaScript, with full type annotations. Generics make collections type-safe.
// Typed arrays
const nums: number[] = [1, 2, 3]
const names: Array<string> = ['Alice', 'Bob']
// Tuples — fixed-length typed arrays
const pair: [string, number] = ['age', 30]
const [key, value] = pair
// Readonly collections
const frozen: readonly number[] = [1, 2, 3]
const frozenTuple: readonly [string, number] = ['a', 1]
// Map with typed keys and values
const map = new Map<string, number>()
map.set('count', 42)
// Set
const ids = new Set<number>([1, 2, 3])
// Record — typed object with uniform value types
const scores: Record<string, number> = {
alice: 95,
bob: 87,
}
// Interface for structured data
interface User {
name: string
age: number
tags: Set<string>
}
// Discriminated unions as data structures
type Tree<T> =
| { kind: 'leaf'; value: T }
| { kind: 'node'; left: Tree<T>; right: Tree<T> } Functions
Functions with typed parameters, return types, and overloads.
// Typed function
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Arrow function with types
const add = (a: number, b: number): number => a + b;
// Optional and default parameters
function power(base: number, exp: number = 2): number {
return base ** exp;
}
// Generic function
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
// Function type
type Callback = (data: string) => void; Conditionals
Same as JavaScript, plus type narrowing and exhaustive checks.
// Type narrowing with if
function process(value: string | number) {
if (typeof value === 'string') {
console.log(value.toUpperCase()); // string
} else {
console.log(value.toFixed(2)); // number
}
}
// Discriminated unions
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.side ** 2;
}
}
// Satisfies operator
const palette = {
red: '#f00',
green: '#0f0',
} satisfies Record<string, string>; Loops
Same loop constructs as JavaScript, with type safety.
// For loop
for (let i = 0; i < 5; i++) {
console.log(i);
}
// For...of with typed arrays
const items: string[] = ['a', 'b', 'c'];
for (const item of items) {
console.log(item); // item is string
}
// For...in
const obj: Record<string, number> = { a: 1, b: 2 };
for (const key in obj) {
console.log(key, obj[key]);
}
// Array methods (type-safe)
[1, 2, 3].map((x): string => x.toString());
[1, 2, 3].filter((x): x is number => x > 1); Generics & Type System
Structural type system with powerful generics, conditional types, mapped types, and template literal types.
// Generic functions
function identity<T>(value: T): T { return value }
identity<number>(42)
identity('hello') // T inferred as string
// Generic constraints
function getLength<T extends { length: number }>(x: T): number {
return x.length
}
// Generic interfaces and classes
interface Repository<T> {
findById(id: string): Promise<T>
save(item: T): Promise<void>
}
class Box<T> {
constructor(public value: T) {}
map<U>(fn: (val: T) => U): Box<U> {
return new Box(fn(this.value))
}
}
// Conditional types
type IsString<T> = T extends string ? true : false
type A = IsString<'hello'> // true
type B = IsString<42> // false
// Mapped types
type Readonly<T> = { readonly [K in keyof T]: T[K] }
type Partial<T> = { [K in keyof T]?: T[K] }
type Pick<T, K extends keyof T> = { [P in K]: T[P] }
// Template literal types
type EventName = `on${Capitalize<'click' | 'focus'>}`
// 'onClick' | 'onFocus'
// infer — extract types
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T
// Variance annotations (4.7+)
interface Producer<out T> { get(): T }
interface Consumer<in T> { accept(value: T): void } Inheritance & Composition
Class inheritance with full type checking. Interfaces and intersection types enable powerful composition patterns.
// Class inheritance
class Animal {
constructor(public name: string) {}
speak(): string { return `${this.name} makes a sound` }
}
class Dog extends Animal {
speak(): string { return `${this.name} barks` }
}
// Abstract classes
abstract class Shape {
abstract area(): number
describe(): string { return `Area: ${this.area()}` }
}
class Circle extends Shape {
constructor(public radius: number) { super() }
area(): number { return Math.PI * this.radius ** 2 }
}
// Interfaces (composition)
interface Serializable {
toJSON(): string
}
interface Printable {
print(): void
}
class Report implements Serializable, Printable {
toJSON() { return '{}' }
print() { console.log(this.toJSON()) }
}
// Intersection types (type-level composition)
type WithId = { id: string }
type WithTimestamp = { createdAt: Date }
type Entity = WithId & WithTimestamp Functional Patterns
All JavaScript functional patterns with full type safety. Generics enable strongly-typed higher-order functions.
// Typed higher-order functions
function map<T, U>(arr: T[], fn: (x: T) => U): U[] {
return arr.map(fn)
}
// Typed composition
const pipe = <T>(...fns: Array<(x: T) => T>) =>
(x: T) => fns.reduce((v, fn) => fn(v), x)
const transform = pipe<number>(
(x) => x * 2,
(x) => x + 1,
)
// Currying with types
const add = (a: number) => (b: number): number => a + b
// Discriminated unions (algebraic data types)
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E }
function map<T, U, E>(
result: Result<T, E>,
fn: (v: T) => U,
): Result<U, E> {
return result.ok
? { ok: true, value: fn(result.value) }
: result
}
// Readonly for immutability
const nums: readonly number[] = [1, 2, 3]
type Immutable<T> = { readonly [K in keyof T]: T[K] } Concurrency
Same event-loop model as JavaScript, with full type safety for async patterns.
// async/await with typed returns
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json() as Promise<User>
}
// Promise.all — typed tuple return
const [users, posts] = await Promise.all([
fetchUsers(), // Promise<User[]>
fetchPosts(), // Promise<Post[]>
])
// users: User[], posts: Post[]
// AbortController — cancellation
const controller = new AbortController()
const res = await fetch('/api/data', {
signal: controller.signal,
})
controller.abort() // Cancel the request
// Async iterators
async function* generateIds(): AsyncGenerator<number> {
let id = 0
while (true) {
yield id++
await new Promise(r => setTimeout(r, 100))
}
}
for await (const id of generateIds()) {
if (id > 10) break
console.log(id)
} Modules & Imports
Uses ES Modules with additional type-only imports. TypeScript compiles to CJS or ESM depending on tsconfig.json. Supports path aliases and declaration files.
// Named exports with types
export const PI: number = 3.14
export function add(a: number, b: number): number {
return a + b
}
export interface User {
name: string
age: number
}
// Default export
export default class App {}
// Named imports
import { add, PI, type User } from './math.ts'
// Type-only import (erased at compile time)
import type { User } from './types.ts'
// Namespace import
import * as math from './math.ts'
// Dynamic import
const mod = await import('./heavy.ts')
// Re-export
export { add } from './math.ts'
export type { User } from './types.ts'
// Ambient module declarations (*.d.ts)
declare module '*.css' {
const styles: Record<string, string>
export default styles
} Error Handling
Same try/catch as JavaScript. Caught errors are unknown by default in strict mode.
// Try / catch with unknown error
try {
JSON.parse('invalid');
} catch (error: unknown) {
if (error instanceof SyntaxError) {
console.error('Parse failed:', error.message);
}
}
// Custom error classes
class AppError extends Error {
constructor(
message: string,
public code: number
) {
super(message);
this.name = 'AppError';
}
}
// Result pattern (no exceptions)
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function safeParse(json: string): Result<unknown> {
try {
return { ok: true, value: JSON.parse(json) };
} catch (e) {
return { ok: false, error: e as Error };
}
} Memory Management
Same GC as JavaScript — TypeScript compiles to JS, so memory management is identical. Types help prevent some leak patterns.
// using declarations (TC39 Stage 3 / TS 5.2+)
// Automatic resource cleanup via Symbol.dispose
class FileHandle implements Disposable {
handle: number
constructor(path: string) { this.handle = openFile(path) }
[Symbol.dispose]() { closeFile(this.handle) }
}
{
using file = new FileHandle('/tmp/data')
// file is automatically disposed at end of block
}
// AsyncDisposable
class Connection implements AsyncDisposable {
async [Symbol.asyncDispose]() {
await this.close()
}
}
await using conn = new Connection()
TypeScript-specific patterns:
- Types help catch null reference issues (
strictNullChecks) WeakRef<T>andWeakMap<K, V>are fully typedusingdeclarations prevent resource leak patterns- No additional memory overhead — types are erased at compile time
- Same debugging tools as JavaScript (Chrome DevTools, Node.js
--inspect)
Performance Profiling
Same runtime profiling as JavaScript. TypeScript adds compile-time performance tools for type checking.
# Runtime profiling — same as JavaScript
node --inspect dist/app.js
# Chrome DevTools, Clinic.js, etc.
# TypeScript compiler performance
npx tsc --generateTrace ./trace
# Open trace in chrome://tracing or
# https://ui.perfetto.dev/
# Measure type-check time
npx tsc --extendedDiagnostics
# Files: 150
# Lines: 25000
# Check time: 2.5s
# Total time: 3.1s
# @typescript/analyze-trace — analyze slow types
npx @typescript/analyze-trace ./trace
// Performance-aware TypeScript patterns
// Avoid deep conditional types (slow type checking)
// Use interface extends over intersection types (&)
// Prefer type aliases for unions over complex mapped types
// Benchmark with Vitest
import { bench, describe } from 'vitest'
describe('sort', () => {
bench('Array.sort', () => {
[3, 1, 2].sort()
})
bench('custom sort', () => {
customSort([3, 1, 2])
})
})
// Run: npx vitest bench Interop
Same runtime interop as JavaScript, plus type declaration files (.d.ts) for typing external libraries and APIs.
// Type declarations for untyped JS libraries
// @types/node, @types/express, etc.
npm install --save-dev @types/node
// Declare types for a JS module without types
declare module 'untyped-lib' {
export function doStuff(input: string): number
}
// Declare global variables (e.g., injected by scripts)
declare global {
interface Window {
analytics: { track(event: string): void }
}
}
// WebAssembly with types
const wasm = await WebAssembly.instantiateStreaming(
fetch('module.wasm')
)
const add = wasm.instance.exports.add as (a: number, b: number) => number
// JSON import (with resolveJsonModule)
import data from './config.json'
// data is fully typed based on JSON structure
// Import assertions
import schema from './schema.json' with { type: 'json' }
// Interop with JSDoc-typed JavaScript
// TypeScript can check .js files with JSDoc annotations
/** @param {string} name */
function greet(name) { return `Hello ${name}` }
// Ambient declarations (*.d.ts files)
// Describe the shape of existing JS code
// without rewriting it in TypeScript Packaging & Distribution
Same npm distribution as JavaScript. Ship .js + .d.ts (type declarations). Use tsup or unbuild for library builds.
# Build a library with tsup (zero-config)
npx tsup src/index.ts --format cjs,esm --dts
# Output: dist/index.js, dist/index.mjs, dist/index.d.ts
# package.json for a TypeScript library
{
"name": "@myorg/lib",
"version": "1.0.0",
"exports": {
".": {
"import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" },
"require": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }
}
},
"files": ["dist"],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"prepublishOnly": "npm run build"
}
}
# Publish
npm publish --access public
# JSR — TypeScript-native registry (Deno/Node.js)
# Publish TypeScript source directly (no build step)
npx jsr publish
# Monorepo publishing
# Turborepo, Nx, or changesets
npx changeset # Create a changeset
npx changeset version # Bump versions
npx changeset publish # Publish to npm