Designed as a strict syntactical superset of JavaScript, adding optional static typing to the language.
Transcompiles to clean, standard JavaScript.
Who:
Anders Hejlsberg — lead architect of C# and creator of Delphi and Turbo Pascal.
Why:
JavaScript was increasingly being used for massive, complex applications, but lacked the robust type safety and object-oriented paradigms necessary to maintain large codebases.
To provide better tooling (IntelliSense, automated refactoring, code navigation).
To allow developers to use modern/future ECMAScript features before browser support.
Introduction
Core Philosophy
TypeScript’s type system is structural, not nominal. This means if an object has the required properties of an interface, it is considered to be of that type, regardless of explicit declaration (often called “duck typing”).
Advantages
Static Typing — Prevents a vast category of runtime errors (e.g., undefined is not a function).
Incredible Developer Experience (DX) — Auto-completion, inline documentation, and instant error highlighting in VS Code.
Object-Oriented & Functional Support — First-class support for classes, interfaces, generics, and higher-order functions.
Ecosystem Compatibility — DefinitelyTyped (@types/*) provides type definitions for almost all popular JS libraries.
Disadvantages
Compilation Overhead — Code must be built/transpiled before execution (though tools like esbuild, swc, and ts-node make this fast).
Complex Type System — Advanced features like conditional types and generic inference can become difficult to read.
False Sense of Security — TypeScript type checks happen at compile time, not runtime. If incoming API data is malformed, TypeScript won’t catch it natively without runtime validation libraries like Zod or Yup.
Basics & Primitives
Primitive Types
let isDone: boolean = false;let decimal: number = 6;let hex: number = 0xf00d;let binary: number = 0b1010;let color: string = "blue";// BigIntlet big: bigint = 100n;// Symbollet sym: symbol = Symbol("key");
// any: Disables type checking (avoid!)let looselyTyped: any = 4;looselyTyped.ifItExists(); // No compiler error// unknown: Type-safe counterpart of any. Forces you to perform type checks.let notSure: unknown = "hello";if (typeof notSure === "string") { console.log(notSure.length); // OK}// void: Absence of any type (usually for function returns)function logMessage(msg: string): void { console.log(msg);}// never: Represents values that never occur (e.g., throwing functions or exhaustive checks)function throwError(msg: string): never { throw new Error(msg);}function infiniteLoop(): never { while (true) {}}
Type Assertions
let someValue: unknown = "this is a string";// 'as' syntax (preferred, especially in JSX/TSX)let strLength: number = (someValue as string).length;// Angle-bracket syntaxlet strLen2: number = (<string>someValue).length;// 'as const' (Const Assertions)// Infers the narrowest possible literal type and makes properties readonly.const config = { endpoint: "https://api.example.com", method: "GET"} as const;// config.method is specifically "GET", not string.
Functions
Typing Parameters and Returns
function add(x: number, y: number): number { return x + y;}// Arrow functionsconst multiply = (x: number, y: number): number => x * y;
Optional, Default, and Rest Parameters
// Optional (?)function greet(name: string, greeting?: string) { return greeting ? `${greeting}, ${name}` : `Hello, ${name}`;}// Defaultfunction pow(value: number, exponent: number = 2): number { return value ** exponent;}// Rest Parametersfunction sum(prefix: string, ...numbers: number[]): string { return prefix + numbers.reduce((a, b) => a + b, 0);}
Function Overloads
TypeScript allows multiple function signatures for a single implementation.
// Overload signaturesfunction makeDate(timestamp: number): Date;function makeDate(m: number, d: number, y: number): Date;// Implementation signaturefunction makeDate(mOrTimestamp: number, d?: number, y?: number): Date { if (d !== undefined && y !== undefined) { return new Date(y, mOrTimestamp - 1, d); } else { return new Date(mOrTimestamp); }}
Interfaces & Type Aliases
Interfaces
Used primarily for defining object shapes.
interface User { id: number; name: string; email?: string; // Optional readonly createdAt: Date; // Readonly}const user: User = { id: 1, name: "Alice", createdAt: new Date() };// user.createdAt = new Date(); // Error
Interfaces with the same name will merge their properties.
interface Window { title: string;}interface Window { ts: TypeScriptAPI;}// Window now has both 'title' and 'ts'
Type Aliases
Used to give a name to any type (primitives, unions, intersections, functions, etc.)
type ID = string | number;type Point = { x: number; y: number;};// Intersection typestype Employee = User & { salary: number };
Interface vs Type
Use interface for public API definitions, class contracts, and object shapes where declaration merging might be useful.
Use type for complex types, unions, intersections, utility types, and tuples.
Classes & Object-Oriented Programming
Basic Class & Access Modifiers
class Person { public name: string; // Accessible anywhere (default) protected age: number; // Accessible within class and subclasses private ssn: string; // Accessible only within this class readonly id: number; // Cannot be changed after initialization constructor(name: string, age: number, ssn: string) { this.name = name; this.age = age; this.ssn = ssn; this.id = Math.random(); }}
Parameter Properties (Shorthand)
class Car { // Automatically creates and assigns 'make' and 'model' constructor(public make: string, private model: string) {}}
Getters and Setters
class Temperature { private _celsius: number = 0; get fahrenheit() { return this._celsius * 9 / 5 + 32; } set fahrenheit(value: number) { this._celsius = (value - 32) * 5 / 9; }}
Abstract Classes
Base classes from which other classes may be derived. They cannot be instantiated directly.
abstract class Shape { abstract getArea(): number; // Must be implemented printArea() { // Optional to override console.log(`Area: ${this.getArea()}`); }}class Circle extends Shape { constructor(public radius: number) { super(); } getArea() { return Math.PI * this.radius ** 2; }}
function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined;}let pet = getSmallPet();if (isFish(pet)) { pet.swim(); // Compiler knows pet is Fish}
Discriminated Unions
Highly useful for Redux actions or state machines.
// Exclude<T, U>: Remove U from Ttype T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"// Extract<T, U>: Intersect T and Utype T1 = Extract<"a" | "b" | "c", "a" | "f">; // "a"// NonNullable<T>: Removes null and undefinedtype T2 = NonNullable<string | number | undefined>; // string | number
Function Utilities
function fetchUser(id: number, active: boolean): { id: number, name: string } { return { id, name: "VR" };}// Parameters<T>: Gets a tuple of function parameterstype FuncParams = Parameters<typeof fetchUser>; // [id: number, active: boolean]// ReturnType<T>: Gets the return typetype UserReturn = ReturnType<typeof fetchUser>; // { id: number, name: string }
Deep Type Systems (Mapped & Conditional Types)
Mapped Types
Creating a new type by iterating over the keys of an existing type.
type Flags = { option1: boolean; option2: boolean;}// Maps over K in Flags, making them all readonlytype ReadonlyFlags = { readonly [K in keyof Flags]: Flags[K];}// Removing Modifiers (using -)type Mutable<T> = { -readonly [P in keyof T]: T[P];};
Conditional Types
Uses a ternary operator syntax at the type level: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;type A = IsString<"hello">; // truetype B = IsString<123>; // false// Nested Conditionaltype TypeName<T> = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : "object";
The infer Keyword
Used within conditional types to extract inner types.
// Extract the type of an Arraytype Flatten<T> = T extends Array<infer U> ? U : T;type C = Flatten<string[]>; // stringtype D = Flatten<number>; // number