thecodex.expert · The Codex Family of Knowledge
Language Reference

TypeScript

JavaScript with a type system — catches bugs before they run, compiles to plain JavaScript, and has become the standard for serious web development.

Language Reference TypeScript 5.x · 2024 Superset of JavaScript Last verified:
New to TypeScript? This is the reference encyclopedia — dip in anytime. To learn step by step, start the TypeScript Course →
Canonical Definition

TypeScript is a statically typed superset of JavaScript, developed by Microsoft, that adds optional type annotations, interfaces, generics, and advanced type system features to JavaScript — compiling to plain JavaScript with all types erased, providing compile-time type safety without any runtime overhead.

📑 TypeScript Reference — All Topics

Types & Interfaces

Primitive types, unions, intersections, type aliases, interfaces.

Generics

Type parameters, generic functions, constraints, defaults.

Utility Types

Partial, Required, Pick, Omit, Record, ReturnType, and more.

What is TypeScript

Led by C#'s architect — now getting a historic Go rewrite in TS 7.0.

Setup and tsconfig

One JSON file controls strictness, target, and which files even get checked.

Basic Types

any turns off checking silently; unknown demands proof before you can use it.

Functions in TypeScript

Wrong-shaped arguments caught before the code runs, not in production.

Union and Intersection Types

| means OR, & means AND — and TypeScript enforces both precisely.

Type Narrowing

A plain typeof check inside an if is enough — zero extra syntax needed.

Enums

The official handbook itself now recommends most new code use unions instead.

Classes in TypeScript

private is a compile-time-only label — the real JS has no privacy at all.

Interfaces vs Types

Nearly interchangeable, except interface can merge and type can't.

Type Inference

Idiomatic TypeScript annotates far less than newcomers expect.

Mapped Types

Partial and Readonly aren't magic — they're ordinary mapped types you can write yourself.

Conditional Types

infer extracts a type from inside another, entirely at compile time.

Type Guards

value is Type teaches TypeScript your own custom narrowing logic.

Decorators

TypeScript 5.0's Stage 3 decorators broke compatibility with pre-2023 decorator code.

Modules and Namespaces

"Prefer modules to namespaces" is the official guidance, stated bluntly.

Declaration Files (.d.ts)

Zero runtime code — pure type description for a plain JavaScript library.

Generic Constraints

extends keyof T restricts a generic to another type's actual property names.

Tuples

Fixed length, per-position types — named elements are docs, not enforcement.

The TypeScript Compiler

Checking and emitting are separate passes — errors don't always block output.

Strict Mode

strictNullChecks alone catches most of the bugs TypeScript is supposed to prevent.

Typing Async Code

async always wraps in Promise — whether you write it explicitly or not.

TypeScript with React

A missing required prop is a compile error, not a blank screen in production.

Testing TypeScript

A test suite can pass in full while real type errors sit right next to it.

Advanced Generics

Multiple type parameters, defaults, and generic classes — the patterns libraries use.

Template Literal Types

Build a type the same way you'd build a template string, since TS 4.1.

tsconfig Deep Dive

target vs lib is the mismatch that compiles fine and crashes at runtime.

One sentence

TypeScript is JavaScript with types — you write almost the same code you would in JavaScript, but the compiler catches mistakes before your code ever runs.

What TypeScript is

TypeScript is a statically typed superset of JavaScript, created by Microsoft (Anders Hejlsberg) and released in 2012. TypeScript compiles to plain JavaScript — any TypeScript program produces a JavaScript file that runs anywhere JavaScript runs: browsers, Node.js, Deno, Bun. The TypeScript compiler (tsc) performs type checking and erases all type annotations from the output. At runtime, there is no TypeScript — only JavaScript.

TypeScript has become the standard for large-scale JavaScript development. React, Vue, Angular, Node.js server code, and most serious frontend tooling are now written in TypeScript. The TypeScript type system is structurally typed (not nominally), has excellent inference, and is notably sophisticated — capable of expressing complex type relationships like conditional types, mapped types, and template literal types.

TypeScriptbasics.ts
// TypeScript adds type annotations to JavaScript
let name: string = "Priya";
let age: number = 28;
let active: boolean = true;

// Type inference — TypeScript infers the type from the value
let city = "Mumbai";   // inferred as string
let count = 0;         // inferred as number

// Function with typed parameters and return type
function greet(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}

// Arrow function with types
const add = (a: number, b: number): number => a + b;

// Objects — inline type annotation
const user: { name: string; age: number } = { name: "Priya", age: 28 };

// Arrays
const scores: number[] = [95, 88, 72];
const names: Array<string> = ["Alice", "Bob"];

Interfaces and type aliases

TypeScript provides two ways to describe the shape of an object: interfaces and type aliases. Both describe what properties an object must have. Interfaces can be extended and merged (declaration merging). Type aliases are more flexible — they can describe union types, intersection types, and primitives, not just object shapes.

TypeScriptinterfaces.ts
// Interface — describes object shape
interface User {
  id: number;
  name: string;
  email: string;
  role?: string;   // ? = optional property
  readonly createdAt: Date;   // cannot be changed after creation
}

// Type alias — more flexible
type ID = string | number;   // union type
type Status = "active" | "inactive" | "pending";  // string literal union

// Extending interfaces
interface AdminUser extends User {
  permissions: string[];
}

// Function that accepts the interface
function printUser(user: User): void {
  console.log(`${user.name} (${user.email})`);
}

// Structural typing — any object with matching shape works
const person = { id: 1, name: "Priya", email: "p@ex.com", createdAt: new Date() };
printUser(person);   // works! structure matches User

Generics

Generics let you write code that works with multiple types while preserving type safety. The classic example: an identity function that returns whatever type it receives.

TypeScriptgenerics.ts
// Generic function — T is a type parameter
function identity<T>(value: T): T {
  return value;
}

const s = identity("hello");   // T inferred as string
const n = identity(42);        // T inferred as number

// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

// Generic constraint — T must have a .length property
function logLength<T extends { length: number }>(value: T): void {
  console.log(`Length: ${value.length}`);
}
logLength("hello");    // 5
logLength([1, 2, 3]); // 3
// logLength(42);      // ERROR: number has no .length

Union types and type narrowing

A union type (string | number) accepts either type. TypeScript narrows the type inside conditionals — after an if (typeof x === "string") check, TypeScript knows x is a string in that branch. This is called type narrowing and is one of TypeScript's most powerful features.

TypeScriptnarrowing.ts
function format(value: string | number | Date): string {
  if (typeof value === "string") {
    return value.toUpperCase();         // TypeScript knows: string
  } else if (typeof value === "number") {
    return value.toFixed(2);            // TypeScript knows: number
  } else {
    return value.toISOString();         // TypeScript knows: Date
  }
}

// Discriminated unions — a union of objects with a shared tag
type Shape =
  | { kind: "circle";    radius: number }
  | { kind: "rectangle"; width: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":    return Math.PI * shape.radius ** 2;
    case "rectangle": return shape.width * shape.height;
  }
  // TypeScript knows this is exhaustive — no default needed
}

Compilation and tsconfig.json

TypeScript is compiled by tsc (the TypeScript compiler). Project settings live in tsconfig.json. Key settings: strict (enables all strict checks — always turn on), target (which JS version to emit: ES2020, ESNext), module (module system: CommonJS, ESM), noImplicitAny (error on implicit any), strictNullChecks (null/undefined are not assignable to other types). For most projects, use the @tsconfig/recommended shared config as a base.

The type system: structural, not nominal

TypeScript uses structural typing: two types are compatible if they have the same shape, regardless of their declared names. If Duck has a quack() method and RubberDuck also has a quack() method, they are mutually assignable — even though they're declared separately. This is the opposite of nominal typing (Java, C#) where two classes are only compatible if one explicitly extends the other.

Structural typing integrates naturally with JavaScript's duck-typing culture but requires understanding: a function that accepts User will also accept any object with the required fields — even objects that have extra fields.

Advanced types: mapped, conditional, template literal

TypeScript's type system is Turing-complete — capable of expressing complex type transformations. Key advanced features:

TypeScriptadvanced_types.ts
// Mapped types — transform every property in a type
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T>  = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
type Record<K extends string, V> = { [P in K]: V };

// Conditional types
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>;  // "yes"
type B = IsString<number>;  // "no"

// Utility types (built into TypeScript)
interface User { id: number; name: string; email: string; }
type UserPreview = Pick<User, "id" | "name">;     // { id; name }
type WithoutEmail = Omit<User, "email">;           // { id; name }
type MaybeUser   = Partial<User>;                 // all optional

// Template literal types
type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;     // "onClick" | "onFocus" | "onBlur"

null, undefined, and strictNullChecks

With strictNullChecks: true (required in strict mode), null and undefined are their own types and are not assignable to other types. A function that might return null must declare string | null as its return type. This forces callers to handle the null case explicitly — eliminating entire categories of null-pointer runtime errors. Without strict null checks, TypeScript behaves like JavaScript — nulls hide everywhere.

TypeScriptnull_safety.ts
function findUser(id: number): User | null {
  const user = db.find(u => u.id === id);
  return user ?? null;
}

const user = findUser(1);
// user.name                  // ERROR: Object is possibly null
if (user !== null) {
  user.name;                  // OK: narrowed to User
}
user?.name;                   // optional chaining: undefined if null
user?.name ?? "Anonymous";    // nullish coalescing: fallback

// Non-null assertion — use only when you KNOW it's not null
const u = findUser(1)!;       // tells TypeScript: "trust me"
u.name;                       // OK — but crashes if actually null

Enums, as const, and string literals

TypeScript has runtime enum objects, but for most use cases as const on an object is preferred — it creates a constant object whose values are inferred as literal types, with no runtime overhead beyond a plain JavaScript object.

TypeScriptenums.ts
// Runtime enum — generates a JS object at runtime
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" }

// Preferred: const object with as const — zero runtime overhead
const DIRECTION = { Up: "UP", Down: "DOWN", Left: "LEFT", Right: "RIGHT" } as const;
type Direction2 = typeof DIRECTION[keyof typeof DIRECTION]; // "UP" | "DOWN" | "LEFT" | "RIGHT"

// String literal union — simplest
type Status = "active" | "inactive" | "pending";
function setStatus(status: Status): void { /* ... */ }
// setStatus("unknown");  // ERROR: Argument of type "unknown" is not assignable
Commonly confused
TypeScript types are erased at runtime — they do not exist in the emitted JavaScript. if (x instanceof User) still works because User is a JavaScript class. But if (typeof x === "MyInterface") will always fail because MyInterface is only a TypeScript type and does not exist at runtime.
TypeScript strict mode is not on by default. A bare tsconfig.json with no settings has almost no type safety. You must explicitly set "strict": true to get strictNullChecks, noImplicitAny, and other essential checks. Always use strict mode.
TypeScript is a superset of JavaScript — all valid JavaScript is valid TypeScript. You can rename a .js file to .ts and it compiles immediately (possibly with type errors, but it compiles). This makes gradual adoption possible — add types incrementally.

The TypeScript type system is Turing-complete

TypeScript's type system, through conditional types, mapped types, and recursive type aliases, is Turing-complete — it can express any computation. This was demonstrated by Henning Hess (2019) who implemented a sorting algorithm entirely in TypeScript's type system. While this is an academic curiosity, it underpins why TypeScript can express types like DeepReadonly<T>, DeepPartial<T>, or route parameter extraction from URL strings: RouteParams<"/users/:id/posts/:postId"> = { id: string; postId: string }.

Variance, covariance, and contravariance

TypeScript's type compatibility follows structural subtyping rules with variance annotations (added in TypeScript 4.7): in (contravariant), out (covariant), in out (invariant). Intuitively: a type used only as a function return is covariant (you can return a more specific type); a type used only as a function parameter is contravariant (you can accept a less specific type). TypeScript infers variance but in/out modifiers make it explicit and allow the compiler to skip expensive structural compatibility checks.

tsc vs. esbuild vs. SWC vs. Babel

tsc (TypeScript compiler): the official compiler — performs full type checking + code emission. Slowest for large projects due to type checking. esbuild: written in Go, strips TypeScript types without type checking, ~100× faster than tsc. Used in Vite. SWC: written in Rust, strips types without type checking, used in Next.js 12+. Babel with @babel/preset-typescript: strips types without checking. The modern pattern: use tsc --noEmit for type checking (in CI) and esbuild/SWC for fast compilation during development and production builds. This separates type checking from transpilation.

Specification reference

TypeScript Language Specification. github.com/microsoft/TypeScript/tree/main/doc. (Note: the spec is informally maintained; the compiler source is authoritative.) TypeScript Handbook: typescriptlang.org/docs/handbook/. Anders Hejlsberg et al. TypeScript 1.0 announcement, April 2014. ECMA-262 (ECMAScript): tc39.es/ecma262/ — TypeScript is a strict superset.

Sources

1
Microsoft. TypeScript Handbook. typescriptlang.org/docs/handbook/. — Authoritative TypeScript reference.
2
Microsoft. TypeScript Language Specification. github.com/microsoft/TypeScript/tree/main/doc.
3
Hejlsberg, A. et al. TypeScript 1.0 announcement. Microsoft Build 2014. — Original language goals and design.
4
Ecma International. ECMAScript 2024 Specification. tc39.es/ecma262/. — TypeScript is a strict superset of ECMAScript.
5
TypeScript GitHub repository. github.com/microsoft/TypeScript. — Compiler source, issues, and release notes.
Source confidence: High Last verified: Primary source: TypeScript Handbook — typescriptlang.org/docs/