C
h
i
L
L
u
.
.
.

TypeScript Mastery Guide

Complete guide from basics to advanced type system

Understanding TypeScript: JavaScript With Superpowers

TypeScript is a strongly typed programming language that builds on JavaScript, developed and maintained by Microsoft. It adds static type definitions to JavaScript, enabling developers to catch errors during development rather than at runtime.

What makes TypeScript powerful is its type system that can express complex relationships while maintaining full compatibility with existing JavaScript code. It compiles to clean, readable JavaScript that runs anywhere JavaScript runs.

Industry Standard: TypeScript is used by companies like Microsoft, Google, Airbnb, and Slack. Its adoption has grown rapidly due to improved developer experience, better tooling, and reduced runtime errors in large-scale applications.

1. TypeScript Basics & Types

Getting Started: Your First TypeScript Program

TypeScript extends JavaScript with type annotations. Here's how to set up and write your first TypeScript program.
// Install TypeScript globally
// npm install -g typescript

// Create a TypeScript file: hello.ts
function greet(name: string): string {
  return `Hello, ${name}!`;
}

const message: string = greet("TypeScript");
console.log(message);

// Compile to JavaScript
// tsc hello.ts
// This generates hello.js

// Basic type annotations
let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
let list: number[] = [1, 2, 3];
let tuple: [string, number] = ["hello", 10];

// Any type (opt-out of type checking)
let notSure: any = 4;
notSure = "maybe a string";
notSure = false;

// Void type (no return value)
function warnUser(): void {
  console.log("This is a warning");
}

// Null and Undefined
let u: undefined = undefined;
let n: null = null;

// Never type (functions that never return)
function error(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

// Type assertions (type casting)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
// Alternative syntax:
// let strLength: number = (<string>someValue).length;

Basic Types and Type Annotations

TypeScript provides several basic types that correspond to JavaScript primitives, along with ways to define custom types.
// Boolean
let isActive: boolean = true;
let isCompleted: boolean = false;

// Number (all floating point)
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;

// String
let color: string = "blue";
color = 'red';
let fullName: string = `Bob Smith`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName} and I'm ${age} years old.`;

// Array
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3]; // Generic array type
let mixed: (string | number)[] = [1, "hello", 2, "world"];

// Tuple (fixed-length array with known types)
let tuple1: [string, number] = ["hello", 10];
// tuple1 = [10, "hello"]; // Error: wrong types
// tuple1[3] = "world"; // Error: index out of bounds

// Enum (named constants)
enum Color {
  Red,
  Green,
  Blue
}
let c: Color = Color.Green;

enum ColorWithValues {
  Red = 1,
  Green = 2,
  Blue = 4
}

enum StringEnum {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

// Object type
let obj: object = {};
let person: { name: string; age: number } = {
  name: "John",
  age: 30
};

// Optional properties
let optionalPerson: {
  name: string;
  age?: number; // Optional property
} = {
  name: "Jane"
};

// Readonly properties
let readonlyPerson: {
  readonly name: string;
  readonly age: number;
} = {
  name: "Bob",
  age: 25
};
// readonlyPerson.name = "Alice"; // Error: readonly

// Index signatures
let dictionary: { [key: string]: number } = {
  "apples": 10,
  "bananas": 5
};

// Type aliases
type Person = {
  name: string;
  age: number;
  email?: string;
};

let user: Person = {
  name: "Alice",
  age: 30
};

// Union types
let id: string | number = "abc123";
id = 123; // Also valid

function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

// Intersection types
type Employee = Person & {
  employeeId: string;
  department: string;
};

let employee: Employee = {
  name: "Bob",
  age: 35,
  employeeId: "E123",
  department: "Engineering"
};

Interfaces: Defining Object Shapes

Interfaces are TypeScript's primary way of defining the structure of objects. They can be extended and implemented by classes.
// Basic interface
interface User {
  id: number;
  name: string;
  email: string;
  age?: number; // Optional property
  readonly createdAt: Date; // Readonly property
}

// Using the interface
let user1: User = {
  id: 1,
  name: "John Doe",
  email: "john@example.com",
  createdAt: new Date()
};

// user1.createdAt = new Date(); // Error: readonly

// Interface with function type
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc = function(src: string, sub: string): boolean {
  return src.search(sub) > -1;
};

// Interface with index signature
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];

// Interface extending another interface
interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
  bark(): void;
}

let myDog: Dog = {
  name: "Buddy",
  age: 3,
  breed: "Golden Retriever",
  bark: () => console.log("Woof!")
};

// Interface extending multiple interfaces
interface Employee {
  employeeId: string;
  department: string;
}

interface Manager extends Person, Employee {
  teamSize: number;
  manage(): void;
}

// Interface for class implementation
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
}

// Hybrid types (object that works as function and object)
interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

function getCounter(): Counter {
  let counter = function (start: number) {} as Counter;
  counter.interval = 123;
  counter.reset = function () {};
  return counter;
}

// Interface vs Type Alias
// Interfaces can be extended and implemented, type aliases can use unions and intersections

interface Square {
  kind: "square";
  size: number;
}

interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}

type Shape = Square | Rectangle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "square":
      return shape.size * shape.size;
    case "rectangle":
      return shape.width * shape.height;
  }
}

// Declaration merging (interfaces only)
interface Box {
  height: number;
  width: number;
}

interface Box {
  scale: number;
}

let box: Box = { height: 5, width: 6, scale: 10 };

Type Inference and Type Compatibility

TypeScript can infer types in many cases, reducing the need for explicit annotations. Understanding type compatibility is key to working with TypeScript effectively.
// Type inference
let x = 3; // TypeScript infers number
let y = "hello"; // TypeScript infers string
let z = [1, 2, 3]; // TypeScript infers number[]

// Contextual typing
window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button); // TypeScript knows mouseEvent is MouseEvent
};

// Best common type inference
let values = [0, 1, null]; // TypeScript infers (number | null)[]

// Type compatibility
interface Named {
  name: string;
}

class Person {
  name: string;
  age: number;
}

let p: Named;
p = new Person(); // OK, because Person has name property

// Function type compatibility
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
// x = y; // Error: y requires two parameters

// Optional parameters and rest parameters
function invokeLater(args: any[], callback: (...args: any[]) => void) {
  callback(...args);
}

invokeLater([1, 2], (x, y) => console.log(x + y));

// Enum compatibility
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };

let status = Status.Ready;
// status = Color.Red; // Error: different enum types

// Class compatibility
class Animal {
  feet: number;
  constructor(name: string, numFeet: number) {}
}

class Size {
  feet: number;
  constructor(numFeet: number) {}
}

let a: Animal = new Animal("dog", 4);
let s: Size = new Size(4);

a = s; // OK
s = a; // OK

// Generic type compatibility
interface Empty<T> {}
let x: Empty<number>;
let y: Empty<string>;

x = y; // OK, because structure is the same

interface NotEmpty<T> {
  data: T;
}
let x2: NotEmpty<number>;
let y2: NotEmpty<string>;

// x2 = y2; // Error: different generic types

// Advanced type inference with conditional types
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false

// Mapped types
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Partial<T> = {
  [P in keyof T]?: T[P];
};

interface Point {
  x: number;
  y: number;
}

type ReadonlyPoint = Readonly<Point>;
// Equivalent to: { readonly x: number; readonly y: number; }

// Keyof operator
type PointKeys = keyof Point; // "x" | "y"

// Typeof operator
function getPoint(): Point {
  return { x: 1, y: 2 };
}
type PointType = typeof getPoint; // () => Point
type ReturnType = ReturnType<typeof getPoint>; // Point

2. Advanced Types

Union and Intersection Types

Union types allow values to be one of several types, while intersection types combine multiple types into one.
// Union types
type ID = string | number;
type Status = "active" | "inactive" | "pending";

function processID(id: ID): string {
  if (typeof id === "string") {
    return id.toUpperCase();
  } else {
    return id.toString();
  }
}

// Discriminated unions (tagged unions)
interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

interface Triangle {
  kind: "triangle";
  base: number;
  height: number;
}

type Shape = Circle | Square | Triangle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    case "triangle":
      return (shape.base * shape.height) / 2;
    default:
      // Exhaustiveness checking
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

// Intersection types
interface BusinessPartner {
  name: string;
  credit: number;
}

interface Identity {
  id: string;
  name: string;
}

interface Contact {
  email: string;
  phone: string;
}

type Employee = Identity & Contact;
type Customer = BusinessPartner & Contact;

// Mixins with intersection types
type BusinessEmployee = BusinessPartner & Employee;

// Conditional types
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<number[]>; // true
type B = IsArray<string>; // false

// Extract and Exclude utility types
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"

// NonNullable utility type
type T2 = NonNullable<string | number | undefined>; // string | number

// Conditional type with infer
type ArrayElementType<T> = T extends (infer U)[] ? U : never;
type ItemType = ArrayElementType<number[]>; // number
type ItemType2 = ArrayElementType<string[]>; // string

// Function return type inference
type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type PromiseType<T> = T extends Promise<infer U> ? U : never;

// Recursive conditional types
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? DeepReadonly<T[P]> 
    : T[P];
};

interface UserProfile {
  name: string;
  settings: {
    theme: string;
    notifications: boolean;
  };
}

type ReadonlyUserProfile = DeepReadonly<UserProfile>;

// Template literal types
type World = "world";
type Greeting = `hello ${World}`; // "hello world"

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

// Mapped types with template literals
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person {
  name: string;
  age: number;
  location: string;
}

type LazyPerson = Getters<Person>;
// {
//   getName: () => string;
//   getAge: () => number;
//   getLocation: () => string;
// }

Utility Types and Type Manipulation

TypeScript provides built-in utility types and powerful type manipulation features to create complex type relationships.
// Partial<T> - Make all properties optional
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type PartialTodo = Partial<Todo>;
// Equivalent to:
// {
//   title?: string;
//   description?: string;
//   completed?: boolean;
// }

// Required<T> - Make all properties required
type RequiredTodo = Required<PartialTodo>;

// Readonly<T> - Make all properties readonly
type ReadonlyTodo = Readonly<Todo>;

// Record<K, T> - Construct object type with property keys K and type T
type PageInfo = {
  title: string;
};

type Page = "home" | "about" | "contact";

const navigation: Record<Page, PageInfo> = {
  home: { title: "Home" },
  about: { title: "About" },
  contact: { title: "Contact" }
};

// Pick<T, K> - Pick set of properties K from T
type TodoPreview = Pick<Todo, "title" | "completed">;
// { title: string; completed: boolean; }

// Omit<T, K> - Construct type by omitting properties K from T
type TodoInfo = Omit<Todo, "completed">;
// { title: string; description: string; }

// Exclude<T, U> - Exclude from T those types that are assignable to U
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<string | number | (() => void), Function>; // string | number

// Extract<T, U> - Extract from T those types that are assignable to U
type T2 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T3 = Extract<string | number | (() => void), Function>; // () => void

// NonNullable<T> - Exclude null and undefined from T
type T4 = NonNullable<string | number | undefined>; // string | number
type T5 = NonNullable<string[] | null | undefined>; // string[]

// Parameters<T> - Obtain parameters of a function type
type T6 = Parameters<(s: string, n: number) => void>; // [string, number]

// ConstructorParameters<T> - Obtain parameters of a constructor function type
type T7 = ConstructorParameters<ErrorConstructor>; // [string?]

// ReturnType<T> - Obtain return type of a function type
type T8 = ReturnType<() => string>; // string

// InstanceType<T> - Obtain instance type of a constructor function type
type T9 = InstanceType<ErrorConstructor>; // Error

// ThisType<T> - Marker for contextual 'this' type
type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>;
};

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return { ...data, ...methods } as D & M;
}

let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx; // Strongly typed this
      this.y += dy; // Strongly typed this
    }
  }
});

// Uppercase, Lowercase, Capitalize, Uncapitalize
type Greeting = "Hello, world";
type ShoutyGreeting = Uppercase<Greeting>; // "HELLO, WORLD"
type QuietGreeting = Lowercase<Greeting>; // "hello, world"
type CapitalizedGreeting = Capitalize<Greeting>; // "Hello, world"
type UncapitalizedGreeting = Uncapitalize<Greeting>; // "hello, world"

// Custom utility types
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

type DeepRequired<T> = {
  [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

type DeepMutable<T> = {
  -readonly [P in keyof T]: T[P] extends object ? DeepMutable<T[P]> : T[P];
};

// Branded types for nominal typing
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, "UserId">;
type ProductId = Brand<string, "ProductId">;

function createUserId(id: string): UserId {
  return id as UserId;
}

function createProductId(id: string): ProductId {
  return id as ProductId;
}

let userId: UserId = createUserId("user-123");
let productId: ProductId = createProductId("product-456");

// userId = productId; // Error: different brands

3. Functions & Classes

Function Types and Overloads

TypeScript provides rich support for function types, including function overloading, optional parameters, and rest parameters.
// Basic function types
function add(x: number, y: number): number {
  return x + y;
}

// Function type expressions
type AddFunction = (x: number, y: number) => number;
const add2: AddFunction = (x, y) => x + y;

// Optional and default parameters
function buildName(firstName: string, lastName?: string): string {
  return lastName ? `${firstName} ${lastName}` : firstName;
}

function buildName2(firstName: string, lastName = "Smith"): string {
  return `${firstName} ${lastName}`;
}

// Rest parameters
function buildName3(firstName: string, ...restOfName: string[]): string {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName3("Joseph", "Samuel", "Lucas", "MacKinzie");

// Function overloading
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}

const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 2023);
// const d3 = makeDate(1, 3); // Error: no overload expects 2 arguments

// This parameter
interface User {
  id: number;
  admin: boolean;
  becomeAdmin: () => void;
}

const user: User = {
  id: 123,
  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  }
};

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

// Generic functions
function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}

// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);

// Multiple type parameters
function map<Input, Output>(
  arr: Input[],
  func: (arg: Input) => Output
): Output[] {
  return arr.map(func);
}

// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

// Constraints on generic types
function longest<Type extends { length: number }>(a: Type, b: Type): Type {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}

// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
// const notOK = longest(10, 100);

// Specifying type arguments
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

// const arr = combine([1, 2, 3], ["hello"]); // Error!
const arr = combine<string | number>([1, 2, 3], ["hello"]); // OK

// Function type inference
function greeter(fn: (a: string) => void) {
  fn("Hello, World");
}

function printToConsole(s: string) {
  console.log(s);
}

greeter(printToConsole);

// Call signatures
type DescribableFunction = {
  description: string;
  (someArg: number): boolean;
};

function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6));
}

// Construct signatures
type SomeConstructor = {
  new (s: string): SomeObject;
};

function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}

Classes and Object-Oriented Programming

TypeScript adds type annotations and visibility modifiers to JavaScript classes, providing full support for object-oriented programming patterns.
// Basic class
class Point {
  x: number;
  y: number;
  
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
  
  scale(n: number): void {
    this.x *= n;
    this.y *= n;
  }
}

// Field declarations
class PointWithDefaults {
  x = 0;
  y = 0;
}

// readonly fields
class Greeter {
  readonly name: string;
  
  constructor(name: string) {
    this.name = name;
  }
}

// Access modifiers: public, private, protected
class Animal {
  public name: string;
  private age: number;
  protected species: string;
  
  constructor(name: string, age: number, species: string) {
    this.name = name;
    this.age = age;
    this.species = species;
  }
  
  public getName(): string {
    return this.name;
  }
  
  private getAge(): number {
    return this.age;
  }
  
  protected getSpecies(): string {
    return this.species;
  }
}

class Dog extends Animal {
  constructor(name: string, age: number) {
    super(name, age, "Canine");
  }
  
  public getInfo(): string {
    return `${this.name} is a ${this.getSpecies()}`; // Can access protected
    // return this.getAge(); // Error: private method
  }
}

// Parameter properties (shorthand)
class PointWithParams {
  constructor(public x: number, public y: number, private z: number) {}
}

// Getters and setters
class Temperature {
  private _celsius: number = 0;
  
  get celsius(): number {
    return this._celsius;
  }
  
  set celsius(value: number) {
    if (value < -273.15) {
      throw new Error("Temperature below absolute zero!");
    }
    this._celsius = value;
  }
  
  get fahrenheit(): number {
    return this._celsius * 1.8 + 32;
  }
  
  set fahrenheit(value: number) {
    this._celsius = (value - 32) / 1.8;
  }
}

// Static properties and methods
class MyClass {
  static staticProperty = "hello";
  static staticMethod() {
    return "world";
  }
}

console.log(MyClass.staticProperty); // "hello"
console.log(MyClass.staticMethod()); // "world"

// Abstract classes
abstract class Department {
  constructor(public name: string) {}
  
  abstract describe(): void;
  
  printName(): void {
    console.log("Department name: " + this.name);
  }
}

class AccountingDepartment extends Department {
  describe(): void {
    console.log("Accounting Department - " + this.name);
  }
}

// Cannot create instance of abstract class
// const dept = new Department(); // Error!
const accounting = new AccountingDepartment("Accounting");

// Implementing interfaces
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
}

// Generic classes
class GenericNumber<NumType> {
  zeroValue: NumType;
  add: (x: NumType, y: NumType) => NumType;
  
  constructor(zeroValue: NumType, add: (x: NumType, y: NumType) => NumType) {
    this.zeroValue = zeroValue;
    this.add = add;
  }
}

const myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);

// Class expressions
const MyClassExpression = class<T> {
  content: T;
  constructor(value: T) {
    this.content = value;
  }
};

const myInstance = new MyClassExpression("hello");

// this-based type guards
class FileSystemObject {
  isFile(): this is FileRep {
    return this instanceof FileRep;
  }
  
  isDirectory(): this is Directory {
    return this instanceof Directory;
  }
}

class FileRep extends FileSystemObject {
  constructor(public path: string, private content: string) {
    super();
  }
}

class Directory extends FileSystemObject {
  children: FileSystemObject[];
  constructor() {
    super();
    this.children = [];
  }
}

function processObject(obj: FileSystemObject) {
  if (obj.isFile()) {
    // obj is FileRep
    console.log(obj.path);
  } else if (obj.isDirectory()) {
    // obj is Directory
    console.log(obj.children);
  }
}

4. Generics & Utility Types

Generic Constraints and Advanced Patterns

Generics allow creating reusable components that work with multiple types while maintaining type safety through constraints.
// Basic generic constraints
interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength([1, 2, 3]); // OK
logLength("hello"); // OK
// logLength(42); // Error: number doesn't have length

// Using type parameters in generic constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let person = { name: "John", age: 30 };
let name = getProperty(person, "name"); // string
let age = getProperty(person, "age"); // number
// let unknown = getProperty(person, "unknown"); // Error

// Generic classes with constraints
class GenericCollection<T extends { id: number }> {
  private items: T[] = [];
  
  add(item: T): void {
    this.items.push(item);
  }
  
  getById(id: number): T | undefined {
    return this.items.find(item => item.id === id);
  }
}

interface User {
  id: number;
  name: string;
}

const userCollection = new GenericCollection<User>();
userCollection.add({ id: 1, name: "John" });

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

const stringResponse: ApiResponse<string> = {
  data: "hello",
  status: 200,
  message: "OK"
};

const defaultResponse: ApiResponse = {
  data: { anything: "goes" },
  status: 200,
  message: "OK"
};

// Conditional types with generics
type IsString<T> = T extends string ? true : false;
type MessageType<T> = T extends { message: string } ? T["message"] : never;

interface Email {
  message: string;
  subject: string;
}

type EmailMessage = MessageType<Email>; // string

// Mapped types with generics
type OptionalFields<T, K extends keyof T> = {
  [P in K]?: T[P];
} & Pick<T, Exclude<keyof T, K>>;

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type ProductPreview = OptionalFields<Product, "description" | "price">;
// { id: number; name: string; price?: number; description?: string; }

// Generic conditional types
type Flatten<T> = T extends any[] ? T[number] : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number

// Infer keyword in conditional types
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Num2 = GetReturnType<() => number>; // number
type Str2 = GetReturnType<(x: string) => string>; // string

// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]

// Non-distributive version
type ToArrayNonDistributive<T> = [T] extends [any] ? T[] : never;
type StrOrNumArr = ToArrayNonDistributive<string | number>; // (string | number)[]

// Template literal types with generics
type EventName<T extends string> = `${T}Changed`;
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`;

type T0 = EventName<"foo">; // "fooChanged"
type T1 = Concat<"Hello", "World">; // "HelloWorld"

// Recursive generic types
type Json = 
  | string
  | number
  | boolean
  | null
  | { [key: string]: Json }
  | Json[];

const jsonData: Json = {
  name: "John",
  age: 30,
  hobbies: ["reading", "gaming"],
  address: {
    street: "123 Main St",
    city: "Anytown"
  }
};

// Generic constraints with keyof
function updateObject<T extends object, K extends keyof T>(
  obj: T,
  key: K,
  value: T[K]
): T {
  return { ...obj, [key]: value };
}

const updatedPerson = updateObject(person, "name", "Jane");

// Generic utility type creation
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? DeepReadonly<T[P]> 
    : T[P];
};

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

type DeepRequired<T> = {
  [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};

// Branded types for nominal typing
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, "UserId">;
type ProductId = Brand<string, "ProductId">;

function createUserId(id: string): UserId {
  return id as UserId;
}

function createProductId(id: string): ProductId {
  return id as ProductId;
}

let userId: UserId = createUserId("user-123");
let productId: ProductId = createProductId("product-456");

// userId = productId; // Error: different brands

Advanced Generic Patterns

TypeScript's generic system supports complex patterns like higher-order functions, type-level programming, and advanced constraint combinations.
// Higher-order generic functions
function compose<A, B, C>(
  f: (a: A) => B,
  g: (b: B) => C
): (a: A) => C {
  return (a: A) => g(f(a));
}

function length(s: string): number {
  return s.length;
}

function double(n: number): number {
  return n * 2;
}

const stringToDoubleLength = compose(length, double);
const result = stringToDoubleLength("hello"); // 10

// Generic interface with multiple type parameters
interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

class Dictionary<K extends string | number, V> {
  private items: KeyValuePair<K, V>[] = [];
  
  set(key: K, value: V): void {
    const index = this.items.findIndex(item => item.key === key);
    if (index >= 0) {
      this.items[index].value = value;
    } else {
      this.items.push({ key, value });
    }
  }
  
  get(key: K): V | undefined {
    return this.items.find(item => item.key === key)?.value;
  }
}

const stringDict = new Dictionary<string, number>();
stringDict.set("age", 30);
stringDict.set("score", 95);

// Conditional type chains
type TypeName<T> = 
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

type T0 = TypeName<string>; // "string"
type T1 = TypeName<() => void>; // "function"
type T2 = TypeName<number[]>; // "object"

// Extract promise types recursively
type Awaited<T> = 
  T extends PromiseLike<infer U> ? Awaited<U> : T;

type P1 = Awaited<Promise<string>>; // string
type P2 = Awaited<Promise<Promise<number>>>; // number
type P3 = Awaited<Promise<string | Promise<number>>>; // string | number

// Union to intersection
type UnionToIntersection<U> = 
  (U extends any ? (k: U) => void : never) extends 
  (k: infer I) => void ? I : never;

type Result = UnionToIntersection<{ a: string } | { b: number }>; 
// { a: string } & { b: number }

// String manipulation types
type GetRouteParams<T extends string> = 
  T extends `${string}:${infer Param}/${infer Rest}` 
    ? Param | GetRouteParams<`${Rest}`> 
    : T extends `${string}:${infer Param}` 
    ? Param 
    : never;

type Params = GetRouteParams<"/users/:id/posts/:postId">; // "id" | "postId"

// Function composition types
type FunctionType = (...args: any[]) => any;

type Compose<F extends FunctionType[]> = 
  F extends [infer First, ...infer Rest]
    ? First extends FunctionType
      ? Rest extends FunctionType[]
        ? (input: Parameters<First>[0]) => ComposeReturn<Rest, ReturnType<First>>
        : never
      : never
    : (input: any) => any;

type ComposeReturn<F extends FunctionType[], Input> = 
  F extends [infer First, ...infer Rest]
    ? First extends FunctionType
      ? Rest extends FunctionType[]
        ? ComposeReturn<Rest, ReturnType<First>>
        : ReturnType<First>
      : Input
    : Input;

// Generic type guards
function isStringArray(value: any): value is string[] {
  return Array.isArray(value) && value.every(item => typeof item === "string");
}

function processValue(value: unknown): void {
  if (isStringArray(value)) {
    // value is string[] here
    value.map(str => str.toUpperCase());
  }
}

// Generic constructor types
interface Constructor<T> {
  new(...args: any[]): T;
}

function createInstance<T>(ctor: Constructor<T>, ...args: any[]): T {
  return new ctor(...args);
}

class Person {
  constructor(public name: string) {}
}

const personInstance = createInstance(Person, "John");

// Mapped types with conditional modifiers
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type Setters<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};

type CompleteObject<T> = T & Getters<T> & Setters<T>;

interface UserData {
  name: string;
  age: number;
}

type CompleteUser = CompleteObject<UserData>;
// {
//   name: string;
//   age: number;
//   getName: () => string;
//   getAge: () => number;
//   setName: (value: string) => void;
//   setAge: (value: number) => void;
// }

// Recursive type constraints
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? T[P] extends Function 
      ? T[P] 
      : DeepReadonly<T[P]>
    : T[P];
};

type DeepMutable<T> = {
  -readonly [P in keyof T]: T[P] extends object 
    ? T[P] extends Function 
      ? T[P] 
      : DeepMutable<T[P]>
    : T[P];
};

💻 TypeScript Practice Projects

Beginner Level

  • 1Build a Type-Safe Calculator with proper function types
  • 2Create a User Management System with interfaces
  • 3Implement a Shopping Cart with type-safe operations
  • 4Build a Form Validation Library with TypeScript types
  • 5Create a Todo App with proper type definitions

Intermediate Level

  • 1Develop a Generic Data Fetching Hook with error handling
  • 2Build a Type-Safe Router with path parameter extraction
  • 3Create a Validation Library with Zod-like type inference
  • 4Implement a State Management System with TypeScript
  • 5Build a Generic API Client with type-safe endpoints

Advanced Level

  • 1Create a Type-Safe SQL Query Builder
  • 2Build a Generic Form Builder with TypeScript
  • 3Implement a Type-Safe Event Emitter System
  • 4Develop a Generic Cache System with type safety
  • 5Build a Type-Safe Plugin System for a framework

📋 TypeScript Quick Reference

Essential Types

  • string, number, boolean - Primitives
  • any, unknown, never - Special types
  • Array<T>, Tuple - Collections
  • interface, type - Object shapes
  • union |, intersection & - Type combinations
  • generic <T> - Reusable types
  • keyof, typeof - Type operators

Utility Types

  • Partial<T> - Make all optional
  • Required<T> - Make all required
  • Readonly<T> - Make all readonly
  • Pick<T, K> - Pick properties
  • Omit<T, K> - Omit properties
  • ReturnType<T> - Function return type
  • Parameters<T> - Function parameters

Master Type-Safe JavaScript Development!

TypeScript brings the power of static typing to JavaScript, enabling you to catch errors early, write more maintainable code, and scale your applications with confidence. Its rich type system and excellent tooling make it an essential skill for modern web development.

With TypeScript, you get the flexibility of JavaScript combined with the safety of static types, making it perfect for large-scale applications, team collaborations, and long-term project maintenance.

Happy TypeScript Coding! 🔷

This comprehensive guide covers TypeScript from basic types to advanced generic patterns and type manipulation.

Essential for modern JavaScript development, large-scale applications, and type-safe programming.

© 2025 TypeScript Mastery Guide | JavaScript That Scales