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
Primitive types, unions, intersections, type aliases, interfaces.
Type parameters, generic functions, constraints, defaults.
Partial, Required, Pick, Omit, Record, ReturnType, and more.
Led by C#'s architect — now getting a historic Go rewrite in TS 7.0.
One JSON file controls strictness, target, and which files even get checked.
any turns off checking silently; unknown demands proof before you can use it.
Wrong-shaped arguments caught before the code runs, not in production.
| means OR, & means AND — and TypeScript enforces both precisely.
A plain typeof check inside an if is enough — zero extra syntax needed.
The official handbook itself now recommends most new code use unions instead.
private is a compile-time-only label — the real JS has no privacy at all.
Nearly interchangeable, except interface can merge and type can't.
Idiomatic TypeScript annotates far less than newcomers expect.
Partial
infer extracts a type from inside another, entirely at compile time.
value is Type teaches TypeScript your own custom narrowing logic.
TypeScript 5.0's Stage 3 decorators broke compatibility with pre-2023 decorator code.
"Prefer modules to namespaces" is the official guidance, stated bluntly.
Zero runtime code — pure type description for a plain JavaScript library.
extends keyof T restricts a generic to another type's actual property names.
Fixed length, per-position types — named elements are docs, not enforcement.
Checking and emitting are separate passes — errors don't always block output.
strictNullChecks alone catches most of the bugs TypeScript is supposed to prevent.
async always wraps in Promise
A missing required prop is a compile error, not a blank screen in production.
A test suite can pass in full while real type errors sit right next to it.
Multiple type parameters, defaults, and generic classes — the patterns libraries use.
Build a type the same way you'd build a template string, since TS 4.1.
target vs lib is the mismatch that compiles fine and crashes at runtime.
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.
// 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.
// 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 UserGenerics
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.
// 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 .lengthUnion 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.
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:
// 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.
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 nullEnums, 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.
// 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 assignableif (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.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..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.
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.