TypeScript Mastery Guide
Complete guide from basics to advanced type system
The Complete TypeScript Mastery Guide
Master static typing, advanced types, and modern JavaScript development with TypeScript's powerful 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
// 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
// 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
// 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
// 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 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
// 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
// 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
// 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
// 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
// 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.