🚀 TypeScript Introduction

🎯 Complete Definition

TypeScript is a strongly typed, object-oriented, compiled programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript that adds optional static typing to the language. TypeScript is designed for the development of large applications and transcompiles to JavaScript.

Key Features: Static typing with type inference, interfaces, classes, modules, decorators, generics, enums, and advanced type systems. TypeScript code is compiled to plain JavaScript, which can run on any browser, Node.js, or any JavaScript engine.

History: Created by Anders Hejlsberg (creator of C#) in 2012. Current version: TypeScript 5.0+. Major adoption by Angular (default), React, Vue.js, and Node.js communities.

Benefits: Early error detection, better code documentation, enhanced IDE support with IntelliSense, refactoring tools, and improved code maintainability for large-scale applications.

Compiler: tsc (TypeScript Compiler) transforms .ts files to .js. Supports various module systems (CommonJS, AMD, ES Modules) and target ECMAScript versions (ES5, ES2015-ES2022).

Type System: Structural typing (duck typing) rather than nominal typing. Type compatibility is based on shape, not name. Supports type inference to reduce verbosity while maintaining type safety.

Ecosystem: Extensive DefinitelyTyped repository for type definitions of JavaScript libraries. Integrated with all major build tools (Webpack, Rollup, Vite) and testing frameworks.

// TypeScript Introduction Example const greeting: string = "Hello, TypeScript!"; const version: number = 5.3; const isAwesome: boolean = true; function welcomeMessage(name: string): string { return `Welcome ${name} to TypeScript Master Track!`; } interface User { id: number; name: string; email?: string; // Optional property } const currentUser: User = { id: 1, name: "CodeOrbit Pro" }; class Application { constructor(public name: string, public version: number) {} launch(): void { console.log(`Launching ${this.name} v${this.version}`); } } const app = new Application("TypeScript App", 1.0); app.launch(); console.log(welcomeMessage("Developer"));

📊 Basic Types

🎯 Complete Definition

TypeScript provides several basic types: boolean, number, string, array, tuple, enum, any, void, null, undefined, never, object, and symbol. Type annotations are added using colon syntax (variable: type).

Boolean: true/false values. Number: All numbers are floating point, includes hexadecimal, binary, and octal literals. String: Text data with support for template strings.

Array: Can be declared as type[] or Array. Tuple: Fixed-length array where each element has a known type. Enum: Named constants that can be numeric or string-based.

Any: Opt-out of type checking - use sparingly. Void: Absence of type, commonly used as function return type. Null/Undefined: Subtypes of all other types when strictNullChecks is false.

Never: Represents values that never occur (function that always throws, infinite loop). Object: Non-primitive type. Unknown: Type-safe counterpart of any (TypeScript 3.0).

Literal Types: Exact values as types (let x: "hello" = "hello"). Union Types: Values that can be one of several types (string | number). Intersection Types: Combines multiple types into one (TypeA & TypeB).

Type Aliases: Create new names for types using type keyword. Type Assertions: Tell compiler "trust me, I know what I'm doing" using as syntax or angle brackets.

Type Inference: TypeScript can infer types when variables are initialized, reducing need for explicit annotations while maintaining type safety.

// Basic TypeScript Types let isDone: boolean = false; let decimal: number = 6; let hex: number = 0xf00d; let binary: number = 0b1010; let name: string = "TypeScript"; let template: string = `Hello ${name}`; // Arrays let list1: number[] = [1, 2, 3]; let list2: Array<number> = [1, 2, 3]; // Tuples - fixed length array let tuple: [string, number]; tuple = ["hello", 10]; // OK // Any - opt-out of type checking let notSure: any = 4; notSure = "maybe a string"; notSure = false; // Void - absence of type function warnUser(): void { console.log("This is a warning"); } // Null and Undefined let u: undefined = undefined; let n: null = null; // Never - represents unreachable code function error(message: string): never { throw new Error(message); } // Type assertions let someValue: any = "this is a string"; let strLength1: number = (someValue as string).length; let strLength2: number = (someValue).length; // Union types let id: string | number; id = "ABC123"; // OK id = 123; // OK // Type aliases type ID = string | number; let userId: ID = "user-123";

🔧 Interfaces

🎯 Complete Definition

Interfaces in TypeScript are powerful way to define contracts within your code and contracts with code outside of your project. They define the shape that values must have and can be used to type-check objects, functions, classes, and more.

Object Shape: Interfaces primarily describe object shapes - the properties they should have and their types. They support optional properties (?), readonly properties, and excess property checks.

Function Types: Interfaces can describe function types by defining a call signature. This specifies the parameter types and return type of a function.

Indexable Types: Interfaces can describe types that can be indexed into like arrays or dictionaries. Use index signatures: [index: type]: returnType.

Class Implementation: Classes can implement interfaces to ensure they meet a particular contract. A class can implement multiple interfaces.

Extending Interfaces: Interfaces can extend other interfaces (single or multiple), inheriting their members. This allows for building up complex interfaces from simpler ones.

Hybrid Types: Interfaces can describe objects that work as both functions and objects with additional properties. Common in JavaScript patterns.

Difference from Type Aliases: Interfaces create a new name that can be used anywhere. Type aliases don't create a new name. Interfaces can be extended and implemented; type aliases can use intersections. Interfaces support declaration merging.

Declaration Merging: When multiple interface declarations with the same name exist, TypeScript merges them into a single interface with combined members. Useful for extending existing interfaces.

Utility: Interfaces provide better error messages, are displayed in IDE tooltips, and work well with IntelliSense. They're essential for defining APIs and library contracts.

interface User { id: number; name: string; email?: string; readonly createdAt: Date; } interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(src: string, sub: string): boolean { return src.search(sub) > -1; }; interface StringArray { [index: number]: string; } class Clock implements ClockInterface { currentTime: Date = new Date(); setTime(d: Date) { this.currentTime = d; } constructor(h: number, m: number) {} } interface Counter { (start: number): string; interval: number; reset(): void; } // Declaration merging interface Box { height: number; width: number; } interface Box { scale: number; } let box: Box = { height: 5, width: 6, scale: 10 };

🏗️ Classes

🎯 Complete Definition

Classes in TypeScript bring traditional object-oriented programming with classes, inheritance, and static typing. TypeScript classes compile to plain JavaScript functions to simulate classes (prior to ES6) or use native ES6 class syntax when targeting ES6+.

Class Members: Properties (fields) and methods. Can have public (default), private, or protected modifiers. TypeScript also adds readonly modifier and parameter properties for concise property declaration.

Constructors: Special method for creating and initializing objects. Can have parameters that automatically become properties using parameter properties (public/private/protected/readonly before parameter name).

Inheritance: Use extends keyword. Derived classes (subclasses) inherit from base classes (superclasses). Can override methods with super keyword to call base class implementation.

Access Modifiers: public (accessible anywhere), private (only within class), protected (within class and derived classes). TypeScript 3.8 adds # for true private fields following ES2022.

Abstract Classes: Cannot be instantiated directly, meant to be base classes. Can contain abstract methods (no implementation) that must be implemented by derived classes.

Static Properties: Belong to class itself, not instances. Accessed via ClassName.propertyName.

Getters/Setters: Accessor functions that look like properties. Useful for validation, computed properties, or encapsulation.

Implementing Interfaces: Classes can implement one or more interfaces, ensuring they provide implementations for all required members.

this Parameter: Functions can have explicitly typed this parameter to ensure correct context. Arrow functions capture this lexically.

Advanced Features: Mixins for multiple inheritance patterns, decorators for meta-programming, and strict property initialization checks.

class Animal { protected name: string; constructor(name: string) { this.name = name; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Person { readonly id: number; constructor(public name: string, id: number) { this.id = id; } } class Employee { private _fullName: string = ""; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (newName.length < 3) { throw new Error("Name must be at least 3 characters"); } this._fullName = newName; } } abstract class Department { constructor(public name: string) {} printName(): void { console.log("Department name: " + this.name); } abstract printMeeting(): void; } class AccountingDepartment extends Department { constructor() { super("Accounting"); } printMeeting(): void { console.log("Meeting at 10am"); } }

⚙️ Functions

🎯 Complete Definition

Functions are the fundamental building block in TypeScript, as in JavaScript. TypeScript adds type annotations to function parameters and return values, enabling better documentation and error checking.

Function Types: Functions can be typed using function type expressions: (param: type) => returnType. This is useful for callbacks and higher-order functions.

Optional and Default Parameters: Parameters can be optional using ? or have default values. Optional parameters must come after required parameters.

Rest Parameters: Collect multiple arguments into an array using ... syntax. Must be the last parameter and typed as an array.

this Parameter: TypeScript can track the type of this in functions. Arrow functions capture this lexically from their surrounding context.

Function Overloads: TypeScript allows multiple function signatures for the same function implementation. Useful for functions that can be called in different ways.

Generic Functions: Functions can have type parameters to work with multiple types while maintaining type safety. Constraints can be applied using extends.

Contextual Typing: TypeScript can infer function parameter types based on context, especially useful for callbacks passed to array methods or event handlers.

Void Return Type: Indicates function doesn't return a value. Different from undefined return type - void allows returning undefined or null (with strictNullChecks off).

Never Return Type: Functions that never return (always throw exception or infinite loop) should have never return type.

Function Declarations vs Expressions: Function declarations are hoisted; function expressions are not. Arrow functions have concise syntax and lexical this.

Immediately Invoked Function Expressions (IIFE): Functions that are executed immediately after creation. Useful for creating scopes.

function add(x: number, y: number): number { return x + y; } function buildName(firstName: string, lastName?: string): string { return lastName ? `${firstName} ${lastName}` : firstName; } function buildName3(firstName: string, ...restOfName: string[]): string { return firstName + " " + restOfName.join(" "); } function reverse(x: number): number; function reverse(x: string): string; function reverse(x: number | string): number | string { if (typeof x === "number") { return Number(x.toString().split("").reverse().join("")); } else { return x.split("").reverse().join(""); } } const multiply = (a: number, b: number): number => a * b; function identity<T>(arg: T): T { return arg; } // IIFE Example (function() { const message = "Hello from IIFE"; console.log(message); })();

🎭 Generics

🎯 Complete Definition

Generics enable creating reusable components that work with multiple types while maintaining type safety. They allow types to be parameters, similar to function parameters.

Generic Functions: Functions that can work with any type. Type parameters are specified in angle brackets before parameters. TypeScript can often infer type arguments from usage.

Generic Interfaces: Interfaces that have type parameters. Can be used to describe objects, functions, or classes that work with multiple types.

Generic Classes: Classes with type parameters. Type parameters can be used for property types, method parameters, and return types.

Generic Constraints: Use extends keyword to constrain type parameters to certain shapes. Ensures type parameter has certain properties or capabilities.

Using Type Parameters in Generic Constraints: Constrain type parameter to be property of another type parameter. Useful for keyof operations.

Generic Defaults: TypeScript 2.3 added default types for generic parameters. Useful when type can be inferred from usage but you want a fallback.

Generic Utility Types: TypeScript provides built-in generic utility types: Partial, Readonly, Record, Pick, Omit, etc.

Mapped Types: Create new types based on old types by transforming properties. Use keyof and indexed access types. Commonly used in utility types.

Conditional Types: Types that depend on type relationships. Syntax: T extends U ? X : Y. Used for type-level logic and inference.

Infer Keyword: Within conditional types, infer can capture types to be used in true branch. Used for extracting types from other types.

Generic Parameter Defaults: Specify default type for generic parameter when not provided. Makes generic types easier to use.

function identity<T>(arg: T): T { return arg; } interface GenericIdentityFn<T> { (arg: T): T; } class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; constructor(zeroValue: T, add: (x: T, y: T) => T) { this.zeroValue = zeroValue; this.add = add; } } interface Lengthwise { length: number; } function loggingIdentity2<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; } type Todo = { title: string; description: string; completed: boolean; }; type PartialTodo = Partial<Todo>; type ReadonlyTodo = Readonly<Todo>; type TodoPreview = Pick<Todo, "title" | "completed">; // Conditional Types type TypeName<T> = T extends string ? "string" : T extends number ? "number" : "object"; // Infer Example type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

🔢 Enums

🎯 Complete Definition

Enums (enumerations) are a TypeScript feature that allows defining a set of named constants. They make code more readable and maintainable by giving meaningful names to numeric or string values.

Numeric Enums: Default enum type where values are auto-incremented numbers starting from 0. Can also manually set values.

String Enums: Each member must be initialized with a string literal or another string enum member. Provide meaningful and readable values.

Heterogeneous Enums: Mix of numeric and string values - generally discouraged as it's rarely useful.

Computed and Constant Members: Enum members can be constant (compile-time known) or computed (runtime expressions).

Reverse Mapping: Numeric enums create reverse mappings from values to names. String enums don't generate reverse mappings.

Const Enums: Use const modifier for enums that are completely removed during compilation. Only constant expressions allowed. Better performance.

Ambient Enums: Used to describe the shape of existing enum types. Useful for declaration files.

Enum Member Types: Each enum member has its own type. Enum types themselves become a union of each enum member.

Enums at Runtime: Enums are real objects that exist at runtime. Can be passed to functions and manipulated.

Enums at Compile Time: TypeScript uses enums for type checking. Const enums are completely removed, replaced by inline values.

Best Practices: Use string enums for better debugging experience. Use const enums for performance-critical code. Avoid heterogeneous enums.

enum Direction { Up = 1, Down, Left, Right } enum StringDirection { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" } const enum ConstEnum { A = 1, B = A * 2 } enum ShapeKind { Circle, Square } interface Circle { kind: ShapeKind.Circle; radius: number; } let direction: Direction = Direction.Up; let directionName: string = Direction[Direction.Up]; // "Up" enum LogLevel { ERROR = "error", WARN = "warn", INFO = "info", DEBUG = "debug" } function log(message: string, level: LogLevel) { console[level](message); } log("Something happened!", LogLevel.INFO);

✨ Advanced Types

🎯 Complete Definition

Advanced Type Features in TypeScript provide powerful type-level programming capabilities that enable sophisticated type safety patterns and abstractions beyond basic types.

Union Types (|): Allow a value to be one of several types. TypeScript will only allow operations that are valid for every type in the union, requiring type guards to narrow types within conditional blocks.

Intersection Types (&): Combine multiple types into one, creating a type that has all properties of each constituent type. Useful for mixin patterns and object composition.

Type Guards: Runtime checks that narrow types within conditional blocks. Built-in guards include typeof, instanceof, and the in operator. Custom type guard functions return "parameter is Type" predicate.

Discriminated Unions: A pattern using a common literal property (discriminant) to distinguish between union members. TypeScript can perform exhaustive type narrowing based on discriminant property values.

Index Types (keyof and T[K]): Use keyof to get a union of property names from a type. Use indexed access T[K] to get the type of a property. Enables type-safe property access patterns.

Mapped Types: Create new types by transforming properties of existing types using {[P in K]: T} syntax. The foundation for utility types like Partial, Readonly, Pick, and Record.

Conditional Types (T extends U ? X : Y): Types that depend on other types through conditional logic. Can be nested for complex type transformations. Essential for type-level programming.

Template Literal Types (TypeScript 4.1+): Use template literal syntax to manipulate string literal types. Enable pattern matching, concatenation, and transformation of string types at compile time.

Infer Keyword: Used within conditional types to capture types for use in the true branch. Essential for extracting types from other types (function parameters, return types, array elements).

Recursive Types: Types that reference themselves, enabling definitions of tree structures, linked lists, and nested data. TypeScript supports both interface and type alias recursion.

Branded/Nominal Types: Patterns using unique phantom properties to create nominal types in TypeScript's structural type system. Prevents accidental mixing of types that have the same structure but different semantics.

satisfies Operator (TypeScript 4.9+): Checks that an expression satisfies a constraint without widening its type to the constraint. Preserves literal types while ensuring type safety.

const Assertions (as const): Create deeply readonly literal types, preserving specific literal values rather than widening to general types. Useful for configuration objects and immutable data.

// Union and Intersection Types type ID = string | number; // Union type Admin = User & Permissions; // Intersection // Type Guards function isString(value: unknown): value is string { return typeof value === "string"; } function processValue(val: string | number) { if (isString(val)) { console.log(val.toUpperCase()); // Type narrowed to string } else { console.log(val.toFixed(2)); // Type narrowed to number } } // Discriminated Union Pattern interface Circle { kind: "circle"; // Discriminant property radius: number; } interface Square { kind: "square"; side: 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.side ** 2; case "triangle": return (shape.base * shape.height) / 2; default: const _exhaustiveCheck: never = shape; return _exhaustiveCheck; } } // Index Types and Mapped Types interface User { id: number; name: string; email: string; age: number; } type UserKeys = keyof User; // "id" | "name" | "email" | "age" type UserPropertyTypes = User[keyof User]; // string | number function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } type Nullable<T> = { [P in keyof T]: T[P] | null }; // Mapped Type type ReadonlyUser = Readonly<User>; // Built-in mapped type // Conditional Types type IsString<T> = T extends string ? true : false; type Result1 = IsString<string>; // true type Result2 = IsString<number>; // false type Flatten<T> = T extends Array<infer U> ? U : T; // Using infer type StrArray = Array<string>; type Str = Flatten<StrArray>; // string // Template Literal Types 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" type GetterName<T extends string> = `get${Capitalize<T>}`; type SetterName<T extends string> = `set${Capitalize<T>}`; type T2 = GetterName<"name">; // "getName" type T3 = SetterName<"age">; // "setAge" // Recursive Types type Json = | string | number | boolean | null | { [key: string]: Json } | Json[]; interface TreeNode<T> { value: T; children: TreeNode<T>[]; } // Branded Types Pattern type Brand<K, T> = T & { readonly __brand: K }; type UserId = Brand<"UserId", string>; type OrderId = Brand<"OrderId", string>; function getUser(id: UserId) { // Implementation } const userId: UserId = "user-123" as UserId; const orderId: OrderId = "order-456" as OrderId; getUser(userId); // OK // getUser(orderId); // Error: Type '"OrderId"' is not assignable to type '"UserId"' // satisfies Operator (TS 4.9+) const colors = { red: "#ff0000", green: "#00ff00", blue: "#0000ff" } satisfies Record<string, string>; // colors.red is still "#ff0000" (not string) const hex: "#ff0000" = colors.red; // Works! // const Assertions const routes = { home: "/", about: "/about", contact: "/contact" } as const; // Deeply readonly with literal types // typeof routes is { readonly home: "/"; readonly about: "/about"; readonly contact: "/contact"; } type Routes = typeof routes; type Path = Routes[keyof Routes]; // "/" | "/about" | "/contact"

📦 Modules

🎯 Complete Definition

Modules in TypeScript provide a way to organize code into reusable, maintainable units with clear boundaries and dependencies. TypeScript supports ES modules (ECMAScript standard) and can compile to various module systems including CommonJS, AMD, SystemJS, and UMD.

Export Declaration: Make variables, functions, classes, interfaces, type aliases, and namespaces available to other modules using the export keyword. Can export individual declarations or use export statements.

Import Declaration: Bring exported declarations from other modules into current module scope using import keyword. Can import specific named exports, default exports, or entire module as namespace.

Default Exports: Each module can have exactly one default export, typically representing the main functionality. Imported without braces using any name. Best for modules that export a single primary thing.

Named Exports: Export multiple values from a module with specific names. Must be imported using matching names in braces or renamed with as keyword. Provide better IntelliSense and refactoring.

Re-exporting (Barrel Files): Export items from another module without importing them first, creating a single entry point that aggregates exports from multiple modules. Useful for library APIs and clean imports.

Namespace Imports: Import all exports from a module into a single namespace object using import * as alias. Useful when module has many exports or to avoid naming conflicts.

Dynamic Imports (import()): Import modules dynamically at runtime using import() function which returns a Promise. Essential for code splitting, lazy loading, and conditional imports.

Module Resolution: Process of resolving module specifiers to actual files. TypeScript supports two strategies: "classic" (legacy) and "node" (Node.js-style). Configured via moduleResolution in tsconfig.json.

Path Mapping: Map module names to specific file paths using baseUrl and paths compiler options. Eliminates relative path complexity with clean aliases like @/ for src/.

Declaration Files (.d.ts): TypeScript files that describe the shape of existing JavaScript modules. Can be handwritten, generated by tsc with --declaration flag, or obtained from DefinitelyTyped repository.

Ambient Modules (declare module): Declare modules that exist but aren't written in TypeScript, typically for JavaScript libraries. Provide type information for untyped modules.

Module Augmentation: Add declarations to existing modules using declare module syntax. Extend third-party module types with additional properties or methods.

ES Modules vs CommonJS: TypeScript can compile to either format using module compiler option. Modern projects prefer ES modules for tree-shaking and future compatibility.

Module Interoperability: Use esModuleInterop and allowSyntheticDefaultImports options for better compatibility between ES modules and CommonJS modules, especially with default imports.

// math.ts - Module with various exports export const PI: number = 3.14159; // Named export export function add(x: number, y: number): number { // Named export return x + y; } export function multiply(x: number, y: number): number { return x * y; } export interface Point { // Named export (interface) x: number; y: number; } export type Vector = [number, number]; // Named export (type alias) export class Calculator { // Named export (class) private value: number = 0; add(x: number): void { this.value += x; } getValue(): number { return this.value; } } // Default export (only one per module) export default function calculateArea(radius: number): number { return PI * radius * radius; } // app.ts - Importing from math module // Import default export (no braces) import calculateArea from "./math"; // Import named exports (with braces) import { PI, add, type Point, Calculator } from "./math"; // Import with rename (using 'as') import { multiply as mult } from "./math"; // Namespace import (all exports as object) import * as MathUtils from "./math"; // Using imports const area = calculateArea(5); const sum = add(10, 20); const product = mult(3, 4); const circleArea = MathUtils.PI * 25; const point: Point = { x: 10, y: 20 }; const calc = new Calculator(); // Re-exporting (barrel file pattern) // index.ts (barrel file) export { add, multiply } from "./math"; export { greet } from "./greetings"; export { default as calculateArea } from "./math"; // Using barrel file import { add, greet, calculateArea } from "./index"; // Dynamic imports (runtime) async function loadMathModule() { // Only load when needed const math = await import("./math"); console.log(math.add(5, 3)); } // Conditional dynamic import if (userNeedsAdvancedFeatures) { import("./advanced").then(advanced => { advanced.enableFeatures(); }); } // Side-effect only import (for polyfills, global styles) import "./styles.css"; import "./polyfills"; // Declaration files (.d.ts) - describing existing JS modules // types.d.ts or external library declarations declare module "my-untyped-library" { export function doSomething(input: string): number; export const defaultConfig: { enabled: boolean }; } // Module augmentation (extending existing modules) // Augmenting a third-party library declare module "third-party-library" { // Add new method to existing interface interface ExistingInterface { newMethod(): void; } // Add new export export function newUtility(): string; } // Ambient module declarations (global) declare module "*.css" { const content: { [className: string]: string }; export default content; } declare module "*.svg" { import React = require("react"); export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>; const src: string; export default src; } // Path mapping in tsconfig.json example (for cleaner imports) /* tsconfig.json: { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"] } } } */ // Using path aliases (after configuring tsconfig.json) import { apiClient } from "@/lib/api"; // instead of "../../../lib/api" import { Button } from "@components/ui"; import { formatDate } from "@utils/helpers"; // Circular dependencies (avoid when possible, but supported) // moduleA.ts import { funcB } from "./moduleB"; export function funcA(): string { return "A calls " + funcB(); } // moduleB.ts import { funcA } from "./moduleA"; export function funcB(): string { return "B calls " + funcA(); }

🎀 Decorators

🎯 Complete Definition

Decorators are a special kind of declaration that can be attached to classes, methods, accessors, properties, or parameters in TypeScript. They use the form @expression where expression evaluates to a function that will be called at runtime with information about the decorated declaration.

Experimental Feature: Decorators are an experimental feature in TypeScript and require enabling with "experimentalDecorators": true in tsconfig.json. They are a Stage 3 proposal for JavaScript with ongoing standardization.

Class Decorators: Applied to class constructors. Can observe, modify, or replace class definition. Receives the constructor function as single parameter. Can return a new constructor to replace the original.

Method Decorators: Applied to method declarations. Can observe, modify, or replace method definition. Receives three parameters: target (prototype or constructor), propertyKey (method name), and descriptor (PropertyDescriptor).

Accessor Decorators: Applied to getter or setter declarations. Similar to method decorators but specifically for property accessors. Can modify get/set behavior.

Property Decorators: Applied to property declarations. Receives two parameters: target (prototype or constructor) and propertyKey (property name). Cannot directly modify property descriptor but can add metadata.

Parameter Decorators: Applied to parameter declarations in constructors or methods. Receives three parameters: target, propertyKey (or undefined for constructor), and parameterIndex. Often used with metadata reflection.

Decorator Factories: Functions that return decorator functions, allowing decorators to be configured with parameters. Enables reusable, configurable decorator patterns.

Decorator Composition: Multiple decorators can be applied to a single declaration. They're evaluated/composed from top to bottom but executed from bottom to top (factory functions run first).

Metadata Reflection: Decorators can be used with the reflect-metadata library to store and retrieve design-time type information. Requires "emitDecoratorMetadata": true in tsconfig.json.

Common Use Cases: Logging, validation, dependency injection, ORM mapping, access control, performance measurement, serialization/deserialization, caching, and aspect-oriented programming.

Framework Adoption: Used extensively in Angular (Component, Injectable, Input decorators), NestJS (Controller, Injectable), TypeORM (Entity, Column), and other TypeScript frameworks for declarative programming.

Limitations & Best Practices: Experimental feature subject to change. Some patterns require reflect-metadata polyfill. Not all decorator types can modify their targets directly. Prefer composition over complex decorator logic.

Stage 3 Proposal: The decorators proposal is at Stage 3 in TC39 process. Future JavaScript versions will have native decorator syntax, potentially different from TypeScript's current implementation.

// Enable in tsconfig.json: "experimentalDecorators": true // Class Decorator - Applied to class constructor function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); console.log(`${constructor.name} class is sealed`); } @sealed class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } // Class decorator that replaces constructor function classLogger<T extends { new(...args: any[]): {} }>(constructor: T) { return class extends constructor { constructor(...args: any[]) { super(...args); console.log(`${constructor.name} instantiated with args:`, args); } }; } @classLogger class Person { constructor(public name: string, public age: number) {} } const person = new Person("Alice", 30); // Logs: "Person instantiated with args: ["Alice", 30]" // Method Decorator - Applied to class methods function enumerable(value: boolean) { // Decorator factory return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { descriptor.enumerable = value; console.log(`Method ${propertyKey} enumerable set to ${value}`); }; } function logExecution( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Calling ${propertyKey} with arguments:`, args); const result = originalMethod.apply(this, args); console.log(`${propertyKey} returned:`, result); return result; }; return descriptor; } class Calculator { @enumerable(false) // Method won't appear in for...in loops @logExecution // Logs method execution add(x: number, y: number): number { return x + y; } @logExecution multiply(x: number, y: number): number { return x * y; } } const calc = new Calculator(); calc.add(5, 3); // Logs execution details // Accessor Decorator - Applied to getters/setters function configurable(value: boolean) { return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { descriptor.configurable = value; }; } class Point { private _x: number = 0; private _y: number = 0; @configurable(false) get x(): number { return this._x; } @configurable(false) get y(): number { return this._y; } set x(value: number) { this._x = value; } set y(value: number) { this._y = value; } } // Property Decorator - Applied to class properties function format(formatString: string) { return function (target: any, propertyKey: string) { let value = this[propertyKey]; const getter = function() { return value; }; const setter = function(newVal: string) { value = formatString.replace("%s", newVal); console.log(`Property ${propertyKey} set to: ${value}`); }; // Replace property with getter/setter Object.defineProperty(target, propertyKey, { get: getter, set: setter, enumerable: true, configurable: true }); }; } function validate(validator: (value: any) => boolean) { return function (target: any, propertyKey: string) { let value = target[propertyKey]; const getter = () => value; const setter = (newVal: any) => { if (!validator(newVal)) { throw new Error(`Invalid value for ${propertyKey}: ${newVal}`); } value = newVal; }; Object.defineProperty(target, propertyKey, { get: getter, set: setter }); }; } class User { @format("Hello, %s!") greeting: string = ""; @validate((val: number) => val >= 0 && val <= 150) age: number = 0; constructor(greeting: string, age: number) { this.greeting = greeting; // Will be formatted this.age = age; // Will be validated } } const user = new User("World", 25); console.log(user.greeting); // "Hello, World!" // Parameter Decorator - Applied to method/constructor parameters function logParameter( target: any, propertyKey: string | undefined, parameterIndex: number ) { console.log(`Parameter ${parameterIndex} of ${propertyKey || "constructor"} decorated`); } function validateParam(validator: (value: any) => boolean) { return function ( target: any, propertyKey: string | undefined, parameterIndex: number ) { // Store validation metadata (usually with reflect-metadata) const existingValidators: ((val: any) => boolean)[] = Reflect.getOwnMetadata("validations", target, propertyKey!) || []; existingValidators[parameterIndex] = validator; Reflect.defineMetadata("validations", existingValidators, target, propertyKey!); }; } class ApiService { fetchData( @logParameter @validateParam((id: any) => typeof id === "string" && id.length > 0) id: string, @validateParam((options: any) => typeof options === "object") options?: object ) { return `Data for ${id}`; } } // Decorator composition - multiple decorators on same declaration @sealed @classLogger class MultiDecoratedClass { @enumerable(false) @logExecution doSomething() { console.log("Doing something..."); } } // Real-world example: Angular-like decorator function Component(config: { selector: string; template: string }) { return function componentDecorator(constructor: Function) { const original = constructor; // Create new constructor const newConstructor: any = function (...args: any[]) { const instance = new original(...args); instance.selector = config.selector; instance.template = config.template; return instance; }; // Copy prototype newConstructor.prototype = original.prototype; return newConstructor; }; } @Component({ selector: "app-user", template: "<div>User Component</div>" }) class UserComponent { name: string = ""; } const userComponent = new UserComponent(); console.log(userComponent.selector); // "app-user"

📁 Namespaces

🎯 Complete Definition

Namespaces (formerly called "internal modules") are a TypeScript-specific way to organize code into logical groups and prevent naming collisions in the global scope. They provide containerization for related functionality.

Purpose & Use Cases: Organize code logically, prevent global namespace pollution, manage dependencies between code parts, and create library APIs. Particularly useful for large applications and libraries before widespread ES module adoption.

Declaration Syntax: Use namespace keyword (or module keyword which is equivalent) followed by name and curly braces containing namespace members. Members must be exported to be accessible from outside.

Exporting Members: Use export keyword on declarations within namespace to make them accessible. Can export variables, functions, classes, interfaces, types, and other namespaces.

Nested Namespaces: Namespaces can contain other namespaces, creating hierarchical organization. Access nested namespaces using dot notation (Namespace.SubNamespace).

Multi-file Namespaces: The same namespace can be split across multiple TypeScript files using /// <reference> directives or module bundlers. All parts contribute to the same namespace.

Ambient Namespaces: Declare namespaces for existing JavaScript libraries using declare namespace syntax in .d.ts files. Provides type information for untyped libraries.

Namespace vs ES Modules: Namespaces are TypeScript-specific using namespace keyword and file merging. ES modules use file-based organization with import/export. Modern TypeScript prefers ES modules.

Triple-Slash Directives (///): Special comments that instruct TypeScript compiler. /// <reference path="..." /> references other files containing namespace declarations. Mostly superseded by modules.

Namespace Merging: Multiple namespace declarations with the same name are automatically merged by TypeScript. Similar to interface merging but for namespaces.

Compilation Output: Namespaces compile to JavaScript objects that create or extend global objects. The --outFile compiler option can bundle multiple files into single output.

When to Use Namespaces: Legacy codebases, certain library patterns (like jQuery plugins), when targeting environments without module support, or when specific bundling behavior is needed.

Limitations & Modern Alternatives: Not part of ECMAScript standard, require special compilation handling, can't be tree-shaken. Modern projects should use ES modules with bundlers like Webpack or Rollup.

Migration Path: Can migrate from namespaces to ES modules by converting namespace exports to module exports, updating references, and using module bundlers for distribution.

// Basic namespace declaration namespace Geometry { export interface Point { x: number; y: number; } export class Circle { constructor(public center: Point, public radius: number) {} area(): number { return Math.PI * this.radius * this.radius; } } // Private to namespace (not exported) const DEFAULT_RADIUS = 1; export function createUnitCircle(center: Point): Circle { return new Circle(center, DEFAULT_RADIUS); } } // Using namespace members const point: Geometry.Point = { x: 10, y: 20 }; const circle = new Geometry.Circle(point, 5); const area = circle.area(); const unitCircle = Geometry.createUnitCircle(point); // Nested namespaces namespace Company { export namespace Departments { export class Engineering { develop() { console.log("Developing product..."); } } export class Sales { sell() { console.log("Selling product..."); } } } export namespace Utils { export function formatCurrency(amount: number): string { return `$${amount.toFixed(2)}`; } } } // Using nested namespaces const engineering = new Company.Departments.Engineering(); engineering.develop(); const formatted = Company.Utils.formatCurrency(99.99); // Multi-file namespace (split across files) // File: shapes/circle.ts /// <reference path="shapes/base.ts" /> namespace Shapes { export class Circle extends Shape { constructor(public radius: number) { super(); } area(): number { return Math.PI * this.radius * this.radius; } } } // File: shapes/base.ts namespace Shapes { export abstract class Shape { abstract area(): number; describe(): string { return `Area: ${this.area()}`; } } } // File: app.ts (main file) /// <reference path="shapes/base.ts" /> /// <reference path="shapes/circle.ts" /> const circle = new Shapes.Circle(5); console.log(circle.describe()); // Namespace merging (multiple declarations merge) namespace Merged { export function funcA() { console.log("Function A"); } } namespace Merged { export function funcB() { console.log("Function B"); } export interface Config { enabled: boolean; } } // Can use all merged members Merged.funcA(); Merged.funcB(); const config: Merged.Config = { enabled: true }; // Ambient namespace declarations (for existing JS libraries) declare namespace ThirdPartyLib { export interface Options { debug?: boolean; timeout?: number; } export function initialize(options?: Options): void; export class Widget { constructor(element: HTMLElement); update(data: any): void; destroy(): void; } } // Using ambient namespace ThirdPartyLib.initialize({ debug: true }); const widget = new ThirdPartyLib.Widget(document.getElementById("app")!); // Namespace with module-like exports namespace MathUtils { export namespace Basic { export function add(a: number, b: number): number { return a + b; } export function subtract(a: number, b: number): number { return a - b; } } export namespace Advanced { export function sin(degrees: number): number { return Math.sin(degrees * Math.PI / 180); } export function cos(degrees: number): number { return Math.cos(degrees * Math.PI / 180); } } } // Alias for namespace (to avoid long references) import BasicMath = MathUtils.Basic; import Trig = MathUtils.Advanced; const sum = BasicMath.add(5, 3); const sineValue = Trig.sin(90); // Compilation output for namespace (JavaScript) /* Compiled JavaScript: var Geometry; (function (Geometry) { var Circle = (function () { function Circle(center, radius) { this.center = center; this.radius = radius; } Circle.prototype.area = function () { return Math.PI * this.radius * this.radius; }; return Circle; }()); Geometry.Circle = Circle; })(Geometry || (Geometry = {})); */ // Namespace with same name as class (common pattern) class Validator { static validateEmail(email: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } } namespace Validator { export function validatePhone(phone: string): boolean { return /^\d{10}$/.test(phone); } } // Using both class and namespace const isValidEmail = Validator.validateEmail("test@example.com"); const isValidPhone = Validator.validatePhone("1234567890");

🛠️ Type Utilities

🎯 Complete Definition

TypeScript's Built-in Type Utilities are generic types that provide powerful tools for common type transformations and manipulations. They're defined in the TypeScript lib files and available globally.

Partial<T>: Constructs a type with all properties of T set to optional. Useful for update operations, partial object creation, and optional configuration.

Required<T>: Constructs a type with all properties of T set to required. The opposite of Partial. Useful when you need to ensure all properties are present.

Readonly<T>: Constructs a type with all properties of T set to readonly. Properties cannot be reassigned after creation. Ensures immutability.

Record<K, T>: Constructs an object type with property keys of type K and property values of type T. Creates dictionary/map-like types with specific key and value types.

Pick<T, K>: Constructs a type by picking the set of properties K from T. Creates a subtype containing only the specified properties.

Omit<T, K>: Constructs a type by omitting the set of properties K from T. The opposite of Pick. Creates a type without the specified properties.

Exclude<T, U>: Excludes from T those types that are assignable to U. Works on union types to remove specific types.

Extract<T, U>: Extracts from T those types that are assignable to U. The opposite of Exclude. Filters union types to keep only matching types.

NonNullable<T>: Excludes null and undefined from T. Useful for ensuring a value is neither null nor undefined.

Parameters<T>: Extracts the parameter types of a function type T as a tuple. Useful for type-safe function composition and higher-order functions.

ReturnType<T>: Extracts the return type of a function type T. Essential for type inference and working with function results.

InstanceType<T>: Extracts the instance type of a constructor function type T. Gets the type of instances created by a class constructor.

ConstructorParameters<T>: Extracts the parameter types of a constructor function type T as a tuple. Similar to Parameters but for constructors.

ThisParameterType<T>: Extracts the type of the this parameter of a function type T, or unknown if the function has no this parameter.

OmitThisParameter<T>: Removes the this parameter from a function type T. Useful for creating function bindings.

Awaited<T> (TypeScript 4.5+): Unwraps a Promise type to get the type of the value it resolves to. Handles nested promises and promise-like types.

Uppercase, Lowercase, Capitalize, Uncapitalize (TypeScript 4.1+): Intrinsic string manipulation types for template literal types. Transform string literal types at compile time.

interface Todo { id: number; title: string; description: string; completed: boolean; createdAt: Date; updatedAt?: Date; } // Partial - all properties optional type PartialTodo = Partial<Todo>; const updateData: PartialTodo = { title: "Updated title", completed: true }; // Can provide any subset of properties function updateTodo(id: number, updates: Partial<Todo>): void { // Update logic here } // Required - all properties required type RequiredTodo = Required<Todo>; const fullTodo: RequiredTodo = { id: 1, title: "Learn TypeScript", description: "Master TypeScript utilities", completed: false, createdAt: new Date(), updatedAt: new Date() // Now required! }; // Readonly - all properties readonly type ReadonlyTodo = Readonly<Todo>; const immutableTodo: ReadonlyTodo = { id: 1, title: "Immutable task", description: "Cannot be modified", completed: false, createdAt: new Date() }; // immutableTodo.title = "Changed"; // Error: Cannot assign to 'title' because it is a read-only property // Record - dictionary type type UserId = string; type UserInfo = { name: string; email: string; }; type UserDictionary = Record<UserId, UserInfo>; const users: UserDictionary = { "user1": { name: "Alice", email: "alice@example.com" }, "user2": { name: "Bob", email: "bob@example.com" } }; // With literal keys type Page = "home" | "about" | "contact"; type PageInfo = { title: string; path: string }; const pages: Record<Page, PageInfo> = { home: { title: "Home", path: "/" }, about: { title: "About", path: "/about" }, contact: { title: "Contact", path: "/contact" } }; // Pick - select specific properties type TodoPreview = Pick<Todo, "id" | "title" | "completed">; const preview: TodoPreview = { id: 1, title: "Preview task", completed: false }; // Only id, title, and completed required // Omit - exclude specific properties type TodoWithoutDates = Omit<Todo, "createdAt" | "updatedAt">; type TodoBasic = Omit<Todo, "description" | "updatedAt">; const basicTodo: TodoBasic = { id: 1, title: "Basic task", completed: false, createdAt: new Date() }; // No description or updatedAt // Exclude - exclude types from union type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" type T1 = Exclude<string | number | (() => void), Function>; // string | number type T2 = Exclude<Todo[keyof Todo], Date>; // number | string | boolean // Extract - extract matching types from union type T3 = Extract<"a" | "b" | "c", "a" | "f">; // "a" type T4 = Extract<string | number | (() => void), Function>; // () => void // NonNullable - exclude null and undefined type T5 = NonNullable<string | number | undefined | null>; // string | number type T6 = NonNullable<Todo["updatedAt"]>>; // Date (not Date | undefined) function assertNonNull<T>(value: T): asserts value is NonNullable<T> { if (value === null || value === undefined) { throw new Error("Value is null or undefined"); } } // Parameters - get function parameters as tuple declare function f1(arg: { a: number; b: string }): void; type T7 = Parameters<typeof f1>; // [{ a: number; b: string; }] type T8 = Parameters<<(s: string) => void>; // [string] type T9 = Parameters<<typeof parseInt>; // [string, (number | undefined)?] // ReturnType - get function return type type T10 = ReturnType<<() => string>; // string type T11 = ReturnType<<typeof Math.random>; // number type T12 = ReturnType<<typeof setTimeout>; // NodeJS.Timeout (or number in browser) function compose<T extends (...args: any[]) => any>(fn: T): ReturnType<T> { // Implementation return fn() as ReturnType<T>; } // InstanceType - get instance type from constructor class C { x = 0; y = 0; } type T13 = InstanceType<typeof C>; // C type T14 = InstanceType<any>; // any type T15 = InstanceType<never>; // never function createInstance<T extends new (...args: any[]) => any>( ctor: T, ...args: ConstructorParameters<T> ): InstanceType<T> { return new ctor(...args); } const instance = createInstance(C); // Type: C // ConstructorParameters - get constructor parameters type T16 = ConstructorParameters<typeof C>; // [] type T17 = ConstructorParameters<typeof Error>; // [string?] type T18 = ConstructorParameters<typeof RegExp>; // [string | RegExp, string?] // Awaited - unwrap Promise type (TypeScript 4.5+) type T19 = Awaited<Promise<string>>; // string type T20 = Awaited<Promise<Promise<number>>>; // number (handles nested promises) type T21 = Awaited<boolean | Promise<number>>; // boolean | number async function fetchData(): Promise<string> { return "data"; } type FetchedData = Awaited<ReturnType<typeof fetchData>>; // string // String manipulation types (TypeScript 4.1+) type T22 = Uppercase<"hello">; // "HELLO" type T23 = Lowercase<"HELLO">; // "hello" type T24 = Capitalize<"hello world">; // "Hello world" type T25 = Uncapitalize<"Hello World">; // "hello World" // Using string manipulation with template literals type EventName<T extends string> = `on${Capitalize<T>}Change`; type T26 = EventName<"click">; // "onClickChange" type T27 = EventName<"input">; // "onInputChange" // Combining multiple utilities type TodoUpdate = Partial<Pick<Todo, "title" | "description" | "completed">>; type TodoIdentifier = Pick<Todo, "id">; type TodoCreate = Omit<Todo, "id" | "createdAt" | "updatedAt">; // Utility for creating strict event handlers type EventMap = { click: MouseEvent; keydown: KeyboardEvent; change: Event; }; type EventHandler<K extends keyof EventMap> = (event: EventMap[K]) => void; function addEventListener<K extends keyof EventMap>( type: K, handler: EventHandler<K> ): void { // Implementation } // Type-safe event handler registration addEventListener("click", (event) => { // event is MouseEvent console.log(event.clientX, event.clientY); }); addEventListener("keydown", (event) => { // event is KeyboardEvent console.log(event.key); });

⚡ Compiler Options

🎯 Complete Definition

TypeScript Compiler Options control how TypeScript code is transpiled to JavaScript, affecting type checking, module resolution, output format, and more. Configured via tsconfig.json file or command-line flags.

Basic Configuration: target (ECMAScript target version), module (module system), lib (library files to include), outDir (output directory), rootDir (root of input files).

Strict Type-Checking Family: strict (enables all strict options), noImplicitAny (error on implied any types), strictNullChecks (strict null/undefined checking), strictFunctionTypes (strict function parameter checking).

Module Resolution: moduleResolution (how modules are resolved - "node" or "classic"), baseUrl (base directory for module resolution), paths (path mapping for module aliases).

Source Map & Declaration: sourceMap (generate .map files), inlineSourceMap (embed source maps in output), declaration (generate .d.ts declaration files), declarationMap (source maps for declarations).

Experimental Features: experimentalDecorators (enable decorator support), emitDecoratorMetadata (emit design-time metadata for decorators).

JavaScript Support: allowJs (allow JavaScript files in compilation), checkJs (type check JavaScript files), jsx (JSX support - "preserve", "react", "react-jsx", "react-native").

Linter-like Checks: noUnusedLocals (error on unused local variables), noUnusedParameters (error on unused parameters), noImplicitReturns (error when not all code paths return a value).

Module Interoperability: esModuleInterop (enable emit interoperability between CommonJS and ES Modules), allowSyntheticDefaultImports (allow default imports from modules with no default export).

Incremental Builds: incremental (enable incremental compilation saving .tsbuildinfo files), composite (enable project references), tsBuildInfoFile (specify .tsbuildinfo file location).

Watch & Build: watch (watch files for changes), preserveWatchOutput (clear screen on recompilation), build (build referenced projects).

Command-Line Interface: tsc --init (generate tsconfig.json), tsc --watch (compile in watch mode), tsc --project (specify config file), tsc --showConfig (show resolved configuration).

Best Practices Configuration: Enable strict mode for production, use incremental builds for large projects, configure paths for clean imports, set appropriate target for your environment.

// Complete tsconfig.json with all major options explained { // ============================================= // COMPILER OPTIONS (core configuration) // ============================================= "compilerOptions": { // ========== LANGUAGE AND ENVIRONMENT ========== "target": "es2022", /* Target ECMAScript version: - "es3", "es5", "es2015" (es6), "es2016", "es2017", - "es2018", "es2019", "es2020", "es2021", "es2022", "esnext" Modern projects should use at least "es2017" or higher */ "lib": ["es2022", "dom", "dom.iterable"], /* Specify library files to include: - JavaScript features: "es5", "es2015", "es2022", etc. - Environment APIs: "dom", "webworker", "scripthost" - Specialized: "esnext", "dom.iterable", "webworker.importscripts" Omit to use default based on target */ "jsx": "react-jsx", /* JSX support: - "preserve": Keep JSX as is in .jsx files - "react": Transform JSX to React.createElement() - "react-jsx": Transform JSX to _jsx() (React 17+) - "react-jsxdev": Development version of react-jsx - "react-native": Keep JSX, output .js files */ // ========== MODULES ========== "module": "esnext", /* Module system for generated code: - "commonjs": Node.js CommonJS - "amd": Asynchronous Module Definition (RequireJS) - "system": SystemJS - "umd": Universal Module Definition - "es2015", "es2020", "esnext": ES Modules - "node16", "nodenext": Node.js ESM Modern: Use "esnext" for bundlers, "commonjs" for Node.js */ "moduleResolution": "node", /* How modules get resolved: - "node": Node.js-style resolution (most common) - "classic": TypeScript's pre-1.6 resolution - "node16", "nodenext": Node.js ESM resolution Usually "node" for most projects */ "baseUrl": "./", /* Base directory to resolve non-relative module names. Used with "paths" option for module aliases. */ "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"], "@types/*": ["src/types/*"] }, /* Module alias/path mapping for cleaner imports. Eliminates "../../../" hell in imports. */ "rootDir": "./src", /* Root directory of input files. Controls output directory structure. */ "typeRoots": ["./node_modules/@types", "./src/types"], /* List of folders to include type definitions from. Default is "./node_modules/@types" */ "types": ["node", "jest", "react"], /* Type declaration files to include. Empty array: include all @types packages. Specific list: only include listed types. */ "resolveJsonModule": true, /* Allow importing .json files as modules. */ "allowUmdGlobalAccess": false, /* Allow accessing UMD globals from modules. */ // ========== JAVASCRIPT SUPPORT ========== "allowJs": true, /* Allow JavaScript files to be compiled. Required for gradual migration from JS to TS. */ "checkJs": true, /* Enable error reporting in .js files. Requires "allowJs": true. */ "maxNodeModuleJsDepth": 1, /* Maximum dependency depth to check in node_modules. 0: don't check, 1: check direct dependencies, etc. */ // ========== EMIT (OUTPUT) ========== "outDir": "./dist", /* Redirect output structure to directory. Preserves input folder structure. */ "outFile": "./dist/bundle.js", /* Concatenate and emit to single file. Only works with "module": "amd" or "system". Consider using bundlers (Webpack, Rollup) instead. */ "sourceMap": true, /* Generate .map source map files for debugging. Maps compiled JS back to original TS. */ "inlineSourceMap": false, /* Embed source maps in output .js files. Alternative to separate .map files. */ "declaration": true, /* Generate .d.ts declaration files. Required for publishing TypeScript libraries. */ "declarationMap": true, /* Generate source maps for .d.ts files. Allows "Go to Definition" to original source. */ "removeComments": false, /* Remove comments from output. Set to true for production to reduce file size. */ // ========== INTEROP CONSTRAINTS ========== "esModuleInterop": true, /* Enable better ES module/CommonJS interop. Emits __importStar and __importDefault helpers. Highly recommended: always set to true. */ "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. Works with "esModuleInterop": true. */ "forceConsistentCasingInFileNames": true, /* Ensure consistent casing in imports. Prevents issues on case-sensitive file systems. */ // ========== TYPE CHECKING (STRICT) ========== "strict": true, /* Enable all strict type-checking options. Foundation of TypeScript's type safety. RECOMMENDED: Always enable for production code. */ "noImplicitAny": true, /* Error on expressions/declarations with implied 'any' type. Part of "strict": true. */ "strictNullChecks": true, /* Enable strict null and undefined checking. Prevents "undefined is not an object" errors. Part of "strict": true. */ "strictFunctionTypes": true, /* Enable strict checking of function types. Catches unsafe function parameter assignments. Part of "strict": true. */ "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods. Part of "strict": true. */ "strictPropertyInitialization": true, /* Ensure class properties are initialized in constructor. Part of "strict": true. */ "noImplicitThis": true, /* Error on 'this' expressions with implied 'any' type. Part of "strict": true. */ "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. Part of "strict": true (since TypeScript 4.4). */ // ========== ADDITIONAL CHECKS ========== "noUnusedLocals": true, /* Error on unused local variables. Helps keep code clean. */ "noUnusedParameters": true, /* Error on unused function parameters. Use underscore prefix (_param) to ignore. */ "noImplicitReturns": true, /* Error when not all code paths return a value. Ensures complete function coverage. */ "noFallthroughCasesInSwitch": true, /* Error on fallthrough cases in switch statements. Requires explicit 'break', 'return', or 'throw'. */ "noUncheckedIndexedAccess": true, /* Add 'undefined' to indexed access types. Makes array[index] and obj[key] return T | undefined. */ "noPropertyAccessFromIndexSignature": false, /* Require index signature access with bracket notation. obj.property vs obj["property"] for index signatures. */ // ========== COMPLETENESS ========== "skipLibCheck": true, /* Skip type checking of declaration files. Speeds up compilation for large projects. */ "skipDefaultLibCheck": true, /* Skip type checking of default library declaration files. Requires "skipLibCheck": true. */ // ========== EXPERIMENTAL ========== "experimentalDecorators": true, /* Enable experimental decorator support. Required for Angular, TypeORM, etc. */ "emitDecoratorMetadata": true, /* Emit design-type metadata for decorators. Requires "experimentalDecorators": true. Used by reflection-based libraries. */ // ========== ADVANCED ========== "incremental": true, /* Enable incremental compilation. Stores compilation info in .tsbuildinfo file. Significantly speeds up subsequent builds. */ "tsBuildInfoFile": "./.tsbuildinfo", /* Specify file to store incremental compilation info. Used with "incremental": true. */ "diagnostics": false, /* Show diagnostic information. For debugging compiler performance. */ "extendedDiagnostics": false, /* Show more detailed diagnostic information. Verbose output for compiler debugging. */ "listFiles": false, /* Print names of compiled files. Useful for debugging include/exclude patterns. */ "listEmittedFiles": false, /* Print names of emitted files. Shows what files are generated. */ "traceResolution": false, /* Log module resolution steps. Debug why a module can't be found. */ // ========== SOURCE MAPS ========== "sourceRoot": "", /* Specify location where debugger should locate TypeScript files. Used with source maps. */ "mapRoot": "", /* Specify location where debugger should locate source map files. Used with source maps. */ "inlineSources": false, /* Include source code in source maps. Creates self-contained source maps. */ }, // ============================================= // FILE SELECTION (what to compile) // ============================================= "include": [ "src/**/*", // All files in src folder "tests/**/*", // Test files "*.ts" // Root .ts files ], "exclude": [ "node_modules", // Always exclude node_modules "dist", // Output directory "**/*.spec.ts", // Test files (if handled separately) "**/*.test.ts", // Test files "**/__tests__/**" // Test directories ], "files": [ // Alternative to include/exclude: explicit file list // "src/index.ts", // "src/main.ts" ], // ============================================= // PROJECT REFERENCES (monorepo/multi-project) // ============================================= "references": [ { "path": "./shared" // Reference to another tsconfig.json }, { "path": "./frontend" // Another project reference }, { "path": "./backend" // Another project reference } ], // ============================================= // EXTENDS (inherit from another config) // ============================================= "extends": "./tsconfig.base.json", /* Inherit configuration from another tsconfig.json. Useful for shared configurations across projects. */ // ============================================= // COMPILE ON SAVE (editor integration) // ============================================= "compileOnSave": true, /* Automatically compile when files are saved. Requires editor support (VS Code has it). */ } // ============================================= // COMMAND LINE USAGE EXAMPLES // ============================================= /* tsc --init # Create tsconfig.json tsc # Compile using tsconfig.json tsc --watch # Watch mode (recompile on changes) tsc --project tsconfig.json # Specify config file tsc src/index.ts # Compile specific file tsc --build # Build referenced projects tsc --showConfig # Show resolved configuration tsc --version # Show TypeScript version # Common flags: # --strict Enable all strict checks # --noEmit Type check only, no output # --outDir ./dist Output directory # --target es2022 ECMAScript target # --module commonjs Module system # --lib es2022,dom Libraries to include */ // ============================================= // ENVIRONMENT-SPECIFIC CONFIGS // ============================================= // tsconfig.dev.json (development) { "extends": "./tsconfig.base.json", "compilerOptions": { "sourceMap": true, "removeComments": false, "noEmitOnError": false } } // tsconfig.prod.json (production) { "extends": "./tsconfig.base.json", "compilerOptions": { "sourceMap": false, "removeComments": true, "noEmitOnError": true }, "exclude": ["**/*.test.ts", "**/*.spec.ts"] } // tsconfig.test.json (testing) { "extends": "./tsconfig.base.json", "compilerOptions": { "types": ["jest", "node"] }, "include": ["src/**/*", "tests/**/*"] }

⚙️ TS Config

🎯 Complete Definition

TypeScript Configuration Files (tsconfig.json) define how TypeScript projects are compiled, including which files to include, compiler options, and project references. Understanding tsconfig.json is essential for any TypeScript project.

File Location & Discovery: Typically placed in project root. TypeScript looks for tsconfig.json starting from current directory up to filesystem root. Can use --project flag to specify config file.

Creation & Initialization: Use tsc --init to generate a tsconfig.json with commented options showing all available settings. Can also create manually for custom configurations.

Configuration Structure: JSON file with compilerOptions object, include/exclude arrays (file selection), files array (explicit file list), references array (project references), and extends property (inheritance).

Include/Exclude Patterns: Glob patterns specifying which files to include/exclude from compilation. Default include: **/* if neither include nor files specified. Always exclude node_modules.

Files Array: Explicit list of files to compile (alternative to include/exclude). Useful for small projects or when precise control over compilation unit is needed.

Extends Property: Inherit configuration from another tsconfig.json file. Creates configuration hierarchy. Useful for sharing common settings across multiple projects (monorepos).

References Array: Configure project references for splitting large codebase into smaller, interdependent projects. Each reference has path to another tsconfig.json.

Watch Options: Configuration for watch mode behavior. Can specify which files/directories to watch, polling strategies, and synchronization settings for better performance.

Build Options: incremental and tsBuildInfoFile for incremental compilation (stores build info between compilations). composite enables project references support.

Type Acquisition: typeRoots and types options control automatic type acquisition from @types packages in DefinitelyTyped repository. Can specify custom type declaration locations.

Best Practices: Use extends for base configurations, enable strict mode for production, configure paths for clean imports, use incremental builds for large projects, set appropriate target version.

Environment-Specific Configs: Create separate configurations for development, production, and testing using extends. Each environment can have different optimizations and checks.

Debugging Configuration: Use tsc --showConfig to see resolved configuration, tsc --traceResolution to debug module resolution, and compiler diagnostics flags for performance analysis.

// ============================================= // BASE CONFIGURATION (shared across projects) // ============================================= // tsconfig.base.json { "$schema": "https://json.schemastore.org/tsconfig", /* JSON schema for editor IntelliSense */ "compilerOptions": { /* Modern ECMAScript target */ "target": "es2022", "lib": ["es2022"], /* Module system */ "module": "esnext", "moduleResolution": "node", /* Strict type checking (ESSENTIAL) */ "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, /* Output configuration */ "declaration": true, "declarationMap": true, "sourceMap": true, /* Linter-like checks */ "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true } } // ============================================= // FRONTEND PROJECT CONFIGURATION // ============================================= // packages/frontend/tsconfig.json { "extends": "../../tsconfig.base.json", "compilerOptions": { /* Frontend-specific settings */ "lib": ["es2022", "dom", "dom.iterable"], "jsx": "react-jsx", /* Path mapping for clean imports */ "baseUrl": ".", "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"], "@hooks/*": ["src/hooks/*"] }, /* Output */ "outDir": "dist", "rootDir": "src", /* Project references support */ "composite": true, "incremental": true, "tsBuildInfoFile": "dist/.tsbuildinfo" }, "include": [ "src/**/*.ts", "src/**/*.tsx", "src/**/*.json" ], "exclude": [ "node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.stories.tsx" ], "references": [ { "path": "../shared" }, { "path": "../design-system" } ] } // ============================================= // BACKEND/NODE.JS PROJECT CONFIGURATION // ============================================= // packages/backend/tsconfig.json { "extends": "../../tsconfig.base.json", "compilerOptions": { /* Node.js specific settings */ "module": "commonjs", "target": "es2020", // Node.js 14+ supports ES2020 "lib": ["es2020"], /* Path mapping */ "baseUrl": ".", "paths": { "@/*": ["src/*"], "@controllers/*": ["src/controllers/*"], "@services/*": ["src/services/*"], "@middleware/*": ["src/middleware/*"] }, /* Output */ "outDir": "dist", "rootDir": "src", /* Node.js module resolution */ "moduleResolution": "node", "types": ["node"], /* Additional checks for backend */ "noUncheckedIndexedAccess": true, /* Project references */ "composite": true, "incremental": true }, "include": [ "src/**/*.ts", "src/**/*.json" ], "exclude": [ "node_modules", "dist", "**/*.test.ts", "**/*.spec.ts" ], "references": [ { "path": "../shared" }, { "path": "../database" } ] } // ============================================= // MONOREPO ROOT CONFIGURATION // ============================================= // tsconfig.json (root) { "files": [], // Empty files array prevents root compilation "references": [ { "path": "./packages/shared" }, { "path": "./packages/frontend" }, { "path": "./packages/backend" }, { "path": "./packages/design-system" }, { "path": "./packages/database" } ] } // ============================================= // DEVELOPMENT VS PRODUCTION CONFIGS // ============================================= // tsconfig.dev.json (development) { "extends": "./tsconfig.json", "compilerOptions": { "sourceMap": true, "inlineSources": true, "removeComments": false, "noEmitOnError": false, // Continue on errors during dev "incremental": true }, "watchOptions": { "watchFile": "useFsEvents", "watchDirectory": "useFsEvents", "fallbackPolling": "dynamicPriority", "synchronousWatchDirectory": true, "excludeDirectories": ["**/node_modules", "dist"] } } // tsconfig.prod.json (production) { "extends": "./tsconfig.json", "compilerOptions": { "sourceMap": false, // No source maps in production "removeComments": true, // Remove comments to reduce size "noEmitOnError": true, // Strict: no output on errors "incremental": false // Disable incremental for clean builds }, "exclude": [ "node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/__tests__/**", "**/__mocks__/**" ] } // ============================================= // TESTING CONFIGURATION // ============================================= // tsconfig.test.json { "extends": "./tsconfig.json", "compilerOptions": { "types": ["jest", "node", "@testing-library/jest-dom"], "esModuleInterop": true, "allowSyntheticDefaultImports": true }, "include": [ "src/**/*", "tests/**/*", "**/*.d.ts" ], "exclude": [ "node_modules", "dist" ] } // ============================================= // LIBRARY/PACKAGE CONFIGURATION // ============================================= // packages/shared/tsconfig.json (library) { "extends": "../../tsconfig.base.json", "compilerOptions": { /* Library output formats */ "declaration": true, "declarationMap": true, "declarationDir": "./dist/types", /* Multiple module outputs (for different environments) */ "outDir": "./dist", /* Path mapping */ "baseUrl": ".", /* Library-specific */ "composite": true, "incremental": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts"] } // ============================================= // CUSTOM TYPE DEFINITIONS CONFIGURATION // ============================================= // tsconfig.json with custom type roots { "compilerOptions": { "typeRoots": [ "./node_modules/@types", // Default @types packages "./src/types", // Project-specific types "./typings" // Custom typings folder ], "types": [ "node", // Always include Node.js types "jest" // Testing framework // Leave empty to include all @types packages ] } } // ============================================= // BUILD SCRIPTS USING TSC // ============================================= /* package.json scripts section */ { "scripts": { /* Basic compilation */ "build": "tsc --project tsconfig.prod.json", "build:dev": "tsc --project tsconfig.dev.json", "build:watch": "tsc --project tsconfig.dev.json --watch", /* Type checking only (no emit) */ "type-check": "tsc --noEmit", "type-check:watch": "tsc --noEmit --watch", /* Project references build */ "build:all": "tsc --build", "build:all:watch": "tsc --build --watch", "clean": "tsc --build --clean", /* Linting/validation */ "validate": "tsc --noEmit --showConfig" } } // ============================================= // TROUBLESHOOTING COMMANDS // ============================================= /* # Show resolved configuration tsc --showConfig # Debug module resolution tsc --traceResolution # Show compiler performance diagnostics tsc --diagnostics # Show extended diagnostics tsc --extendedDiagnostics # List all compiled files tsc --listFiles # List all emitted files tsc --listEmittedFiles # Create a minimal config tsc --init --force # Check specific file tsc --noEmit src/index.ts */

⏳ Async/Await

🎯 Complete Definition

Async/Await is syntactic sugar built on top of Promises in TypeScript/JavaScript, making asynchronous code look and behave more like synchronous code while maintaining non-blocking execution.

Async Functions: Functions declared with async keyword. Always return a Promise. If the function returns a value, the Promise resolves to that value. If the function throws an exception, the Promise rejects with that exception.

Await Expressions: Can only be used inside async functions. Pauses execution of the async function until the Promise settles. Returns the resolved value if the Promise fulfills, throws the rejection reason if the Promise rejects.

Error Handling: Use try/catch blocks with await to handle rejected Promises. Rejected Promises throw exceptions that can be caught synchronously. Alternative: use .catch() on the Promise chain.

Parallel Execution: Use Promise.all() to await multiple promises concurrently (all must succeed). Promise.race() for the first settled promise. Promise.allSettled() for all promises (regardless of success/failure). Promise.any() for first fulfilled promise.

Type Inference: TypeScript infers the Promise return type from async function return value. await expression has the type of the Promise's resolved value (unwrapped).

Promise<T> Type: Generic type representing an asynchronous operation that will eventually produce a value of type T (or never). Essential for typing async operations.

Async Iteration (for await...of): Loop for iterating over async iterables (objects with [Symbol.asyncIterator] method). Used with async generators and streams.

Async Generators (async function*): Functions that can yield multiple values asynchronously. Return AsyncGenerator object. Use yield to produce values, await to consume async operations.

Top-Level Await (TypeScript 3.8+): Support for await at the top level in modules (not in scripts). No need for async wrapper function in module context. Enables module initialization with async operations.

AbortController & AbortSignal: Mechanism for canceling async operations. Pass AbortSignal to abortable APIs like fetch(). Call abort() to cancel ongoing operations.

Error Propagation: Unhandled Promise rejections in async functions propagate to the caller. Use global unhandledrejection event handler to catch unhandled rejections.

Performance Considerations: Each await creates a microtask. Avoid await in tight loops; consider batching operations. Use Promise.all() for independent parallel operations.

Best Practices: Always handle errors, use Promise.all() for parallel independent operations, avoid nesting async functions excessively, use async/await over raw Promise chains for readability.

// Basic async/await example async function fetchData(url: string): Promise<string> { const response = await fetch(url); // Wait for fetch Promise if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.text(); // Wait for text() Promise return data; // Automatically wrapped in Promise } // Async function always returns a Promise async function getNumber(): Promise<number> { return 42; // Equivalent to Promise.resolve(42) } async function throwError(): Promise<never> { throw new Error("Failed"); // Equivalent to Promise.reject(new Error("Failed")) } // Error handling with try/catch async function fetchWithErrorHandling() { try { const data = await fetchData("https://api.example.com"); console.log("Success:", data); return { success: true, data }; } catch (error) { console.error("Error fetching data:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } finally { console.log("Fetch attempt completed"); } } // Alternative error handling with .catch() async function fetchWithCatch() { const data = await fetchData("https://api.example.com") .catch(error => { console.error("Caught error:", error); return "Default data"; // Provide fallback }); console.log("Data:", data); } // Parallel execution with Promise.all() async function fetchMultipleUrls(urls: string[]): Promise<string[]> { const promises = urls.map(url => fetchData(url)); return await Promise.all(promises); // Wait for all promises in parallel } // Promise.race() - first settled promise wins async function fetchWithTimeout(url: string, timeoutMs: number): Promise<string> { const fetchPromise = fetchData(url); const timeoutPromise = new Promise<string>((resolve, reject) => { setTimeout(() => reject(new Error("Timeout")), timeoutMs); }); return await Promise.race([fetchPromise, timeoutPromise]); } // Promise.allSettled() - wait for all, regardless of success/failure async function fetchAllSettled(urls: string[]): Promise<PromiseSettledResult<string>[]> { const promises = urls.map(url => fetchData(url)); return await Promise.allSettled(promises); } // Promise.any() - first fulfilled promise (TypeScript 4.1+) async function fetchFromAnyServer(urls: string[]): Promise<string> { const promises = urls.map(url => fetchData(url)); return await Promise.any(promises); // Returns first successful fetch } // Type inference with async/await async function getUser(id: number) { const response = await fetch(`/api/users/${id}`); const user: { id: number; name: string; email: string } = await response.json(); return user; // Return type inferred as Promise<{ id: number; name: string; email: string }> } // Explicit return types for clarity async function getUserById(id: number): Promise<{ id: number; name: string }> { // Implementation return { id, name: "John Doe" }; } // Async iteration with for await...of async function processUrlsSequentially(urls: string[]) { for (const url of urls) { try { const data = await fetchData(url); // Process one at a time console.log("Processed:", data); } catch (error) { console.error("Failed to process", url, error); } } } // Async generator function async function* asyncNumberGenerator(limit: number): AsyncGenerator<number> { for (let i = 0; i < limit; i++) { // Simulate async operation await new Promise(resolve => setTimeout(resolve, 100)); yield i; } } // Using async generator with for await...of async function consumeAsyncGenerator() { for await (const num of asyncNumberGenerator(5)) { console.log("Received:", num); } } // Top-level await (in modules only) // In a module file (not script) const data = await fetchData("https://api.example.com/data"); console.log("Module initialized with data:", data); // Export can use top-level await export const initializedData = await fetchData("https://api.example.com/config"); // AbortController for cancellation async function fetchWithAbort(url: string, signal: AbortSignal): Promise<string> { const response = await fetch(url, { signal }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.text(); } // Using abort controller async function fetchWithTimeoutUsingAbort(url: string, timeoutMs: number) { const controller = new AbortController(); const signal = controller.signal; // Set timeout to abort const timeoutId = setTimeout(() => controller.abort(), timeoutMs); try { const result = await fetchWithAbort(url, signal); clearTimeout(timeoutId); return result; } catch (error) { clearTimeout(timeoutId); if (error instanceof DOMException && error.name === "AbortError") { throw new Error("Request timed out"); } throw error; } } // Retry logic with exponential backoff async function fetchWithRetry( url: string, maxRetries: number = 3, initialDelay: number = 1000 ): Promise<string> { let lastError: Error; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fetchData(url); } catch (error) { lastError = error as Error; if (attempt === maxRetries) { break; } // Exponential backoff const delay = initialDelay * Math.pow(2, attempt); console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error(`Failed after ${maxRetries + 1} attempts: ${lastError!.message}`); } // Concurrent processing with limited concurrency async function processWithConcurrency<T, R>( items: T[], processor: (item: T) => Promise<R>, concurrency: number = 5 ): Promise<R[]> { const results: R[] = []; const queue = [...items]; async function worker() { while (queue.length > 0) { const item = queue.shift()!; try { const result = await processor(item); results.push(result); } catch (error) { console.error(`Failed to process item: ${error}`); } } } // Start workers const workers = Array(Math.min(concurrency, items.length)) .fill(null) .map(() => worker()); // Wait for all workers to finish await Promise.all(workers); return results; } // Utility types with async/await type ApiResponse = Awaited<ReturnType<typeof fetchData>>; // string async function processInSequence<T, R>( items: T[], callback: (item: T) => Promise<R> ): Promise<R[]> { const results: R[] = []; for (const item of items) { const result = await callback(item); // Process sequentially results.push(result); } return results; } // Global error handling for unhandled rejections if (typeof window !== 'undefined') { window.addEventListener('unhandledrejection', event => { console.error('Unhandled promise rejection:', event.reason); event.preventDefault(); // Prevent browser error logging }); } // Async function that never returns (infinite loop) async function runForever(): Promise<never> { while (true) { await new Promise(resolve => setTimeout(resolve, 1000)); console.log("Still running..."); } }