Compare

Lang Compare

TypeScript

Web

Overview

Typed superset of JavaScript that compiles to plain JS. Adds static types, interfaces, and powerful tooling to JavaScript.

Resources

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
  • tsc performs full type checking but is slow for large projects
  • Most build tools (esbuild, SWC, Bun) only strip types — no type checking
  • --isolatedDeclarations enables parallel .d.ts generation
  • Node.js 22+ can run .ts files 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> and WeakMap<K, V> are fully typed
  • using declarations 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