The site is still under development.

Typescript Tutorial

1.1 What is TypeScript

TypeScript is a superset of JavaScript that introduces static typing and modern features to enhance the development experience. It allows developers to write safer code by catching errors at compile time rather than runtime. Unlike plain JavaScript, TypeScript enforces type checks, making large applications more maintainable and less prone to bugs. It compiles down to standard JavaScript, meaning it can run anywhere JavaScript runs, such as in browsers or on Node.js. TypeScript bridges the gap between rapid JavaScript development and strongly-typed languages like Java or C#.

// Example: Basic TypeScript variable
type User = { name: string, age: number };
let person: User = { name: "Alice", age: 25 };
console.log(person);
1.2 History and Evolution

TypeScript was developed by Microsoft and first released in 2012 to address JavaScript’s shortcomings in large-scale application development. Anders Hejlsberg, also the creator of C#, led the project. Over the years, TypeScript evolved into one of the most popular programming languages, gaining traction because it enhances JavaScript with optional static typing, interfaces, classes, and tooling support. Today, it’s widely adopted by companies like Google (Angular framework uses it), Slack, and Airbnb. The strong developer community and Microsoft’s backing ensure continuous updates and improvements.

// Example: Showing evolution with class syntax
class Car {
  brand: string;
  constructor(brand: string) {
    this.brand = brand;
  }
}
let myCar = new Car("Toyota");
console.log(myCar.brand);
1.3 TypeScript vs JavaScript

TypeScript and JavaScript are closely related but differ in key aspects. JavaScript is a dynamically typed scripting language, while TypeScript is its statically typed superset. TypeScript adds features like interfaces, generics, enums, and compile-time error checking, making development safer and scalable. However, since TypeScript must compile down to JavaScript, developers still get the full flexibility of JavaScript. Beginners may find JavaScript simpler to start with, but TypeScript is invaluable for larger projects where type safety and maintainability matter.

// Example: TypeScript vs JavaScript
// JavaScript: let num = 5;
// TypeScript with typing:
let num: number = 5;
// num = "string"; // Compile-time error
1.4 Benefits of using TypeScript

The primary benefits of TypeScript include type safety, better tooling support, enhanced readability, and reduced runtime errors. It helps catch mistakes early during development, which saves debugging time. TypeScript integrates seamlessly with IDEs, providing autocomplete, intelligent suggestions, and refactoring tools. It also makes collaboration easier in teams by providing clear contracts via type annotations. For large projects, TypeScript ensures consistent coding practices and makes codebases more robust, leading to fewer bugs in production and improved long-term maintainability.

// Example: Autocomplete with types
function greet(name: string) {
  return `Hello, ${name}`;
}
console.log(greet("Majid"));
1.5 Setting up Development Environment

Setting up a TypeScript development environment involves installing the necessary tools and configuring the workspace. Developers usually start with Node.js, which allows running JavaScript outside the browser. Then, they install the TypeScript compiler globally or within their project. An editor like Visual Studio Code is highly recommended, as it has first-class TypeScript support, including IntelliSense and debugging. The setup ensures developers can write `.ts` files, compile them to `.js`, and run them seamlessly, enabling a smooth workflow.

// Example: Initialize a project
npm init -y
npm install typescript --save-dev
tsc --init
1.6 Installing Node.js

Node.js is essential for running JavaScript outside browsers and managing TypeScript projects with npm (Node Package Manager). Installing Node.js provides both the runtime and npm, which helps in installing dependencies like TypeScript. Developers can download it from the official Node.js website, choosing either the LTS (Long-Term Support) version for stability or the Current version for the latest features. Once installed, verifying the installation with node -v and npm -v ensures the environment is ready for TypeScript development.

// Example: Check installation
node -v
npm -v
1.7 Installing TypeScript Compiler (tsc)

The TypeScript compiler, abbreviated as tsc, converts `.ts` files into standard JavaScript files. It can be installed globally using npm install -g typescript or locally in a project as a dev dependency. Developers then use the tsc command in the terminal to transpile TypeScript into JavaScript. A configuration file, tsconfig.json, can be created to specify compiler options such as target JavaScript version, module system, and strict type checking rules.

// Example: Install compiler
npm install -g typescript
tsc file.ts
1.8 Using Visual Studio Code for TypeScript

Visual Studio Code (VS Code) is the most popular IDE for TypeScript because it offers rich support out of the box. It provides features like syntax highlighting, error detection, and code completion powered by the TypeScript language service. Developers can debug TypeScript code directly in VS Code, set breakpoints, and inspect variables. Extensions further enhance productivity, allowing integration with frameworks like Angular, React, or Node.js. Its lightweight design and cross-platform compatibility make it the ideal editor for TypeScript development.

// Example: Run TypeScript inside VS Code
// Open VS Code terminal
tsc app.ts
node app.js
1.9 Writing First TypeScript File

Writing the first TypeScript file usually starts with creating a `.ts` file containing typed variables or functions. This allows developers to see how TypeScript enforces type safety. A simple “Hello World” program demonstrates the basics. After writing the code, developers use tsc to compile it into JavaScript, which can then be executed with Node.js. This initial step helps beginners understand the workflow and experience the immediate benefits of static typing compared to plain JavaScript.

// Example: hello.ts
let message: string = "Hello, TypeScript!";
console.log(message);
1.10 Compiling TypeScript to JavaScript

Compiling is the process where the TypeScript compiler translates `.ts` files into `.js`. This ensures compatibility with any JavaScript environment. Developers can compile a file using the tsc filename.ts command. If a project contains many TypeScript files, a tsconfig.json file makes compiling easier with project-wide settings. The compiled JavaScript file reflects the logic written in TypeScript but without type annotations, making it runnable in browsers or Node.js without requiring special runtimes.

// Example: Compile a file
tsc hello.ts
// Output: hello.js
1.11 Running Compiled JavaScript

After compilation, the generated JavaScript can be executed in any JavaScript runtime, including browsers or Node.js. Typically, developers run it in Node.js for quick testing using node filename.js. Since TypeScript compiles down to plain JavaScript, the output file is compatible with all existing libraries and frameworks. This separation of writing in TypeScript and running in JavaScript demonstrates the practicality of using TypeScript without sacrificing ecosystem compatibility.

// Example: Run compiled JS
node hello.js
1.12 TypeScript Playground Overview

The TypeScript Playground is an online tool provided by Microsoft where developers can experiment with TypeScript code without any setup. It allows writing TypeScript in the browser and instantly shows the compiled JavaScript output. This is useful for learning, prototyping, and sharing examples. It also provides options to configure compiler settings, try out experimental features, and share links with others. Beginners often use it to quickly test ideas before setting up a local environment.

// Example: Try in Playground
let x: number = 42;
console.log(x * 2);
1.13 TypeScript Versioning

TypeScript follows semantic versioning (semver), where updates may introduce new features, improvements, or bug fixes. Staying updated ensures access to the latest language features, like newer syntax improvements and better type-checking options. Developers can check their installed version with tsc -v. Since TypeScript evolves rapidly, many frameworks recommend specific versions for compatibility. Using a package manager like npm, developers can install or upgrade to specific versions as needed to align with project requirements.

// Example: Check version
tsc -v
npm install typescript@latest --save-dev
1.14 TypeScript Community and Resources

The TypeScript community is vibrant and growing rapidly, supported by Microsoft, open-source contributors, and major frameworks like Angular. Developers can find resources through the official TypeScript documentation, GitHub repositories, online courses, and forums like Stack Overflow. The community is also active on platforms like Reddit and Discord. Continuous contributions from developers worldwide ensure that TypeScript evolves to meet modern demands. Engaging with this community helps developers stay updated with best practices, patterns, and problem-solving strategies.

// Example: Find resources
// Official site: https://www.typescriptlang.org
// GitHub: https://github.com/microsoft/TypeScript
1.15 Real-world Applications

TypeScript is widely used in real-world applications, especially for large-scale projects where maintainability and scalability are crucial. Companies like Microsoft, Google, Slack, and Airbnb rely on it. Frameworks such as Angular are written in TypeScript, while React and Node.js projects often adopt it for reliability. Developers use TypeScript in web development, backend services, mobile apps, and even desktop applications with frameworks like Electron. Its versatility and type safety make it a preferred choice across industries.

// Example: React with TypeScript
import React from "react";
type Props = { name: string };
const Welcome = ({ name }: Props) => <h1>Hello, {name}</h>>;
export default Welcome;

2.1 Variables (let, const, var)

In TypeScript, variables can be declared using let, const, or var. The keyword let is block-scoped and is most commonly used for variables that may change their value. The const keyword is also block-scoped but represents values that should never be reassigned, making code more predictable. On the other hand, var has function scope and can lead to unexpected behavior, so its usage is generally discouraged in modern TypeScript. Using let and const makes the code safer and more maintainable, especially in large applications.

<script>
  let name: string = "Alice";
  const pi: number = 3.14159;
  var age: number = 30;
  console.log(name, pi, age);
</script>
      

2.2 Data types overview

TypeScript provides a rich set of data types to bring static type checking into JavaScript code. The core types include string, number, boolean, array, tuple, enum, null, undefined, any, unknown, and void. Using these types, developers can precisely describe the shape and intent of data, making the code self-documenting and reducing runtime errors. By leveraging type annotations, one can enforce correct variable usage and function signatures, enabling TypeScript to catch bugs before execution.

<script>
  let username: string = "Bob";
  let score: number = 100;
  let isActive: boolean = true;
</script>
      

2.3 Strings and string templates

Strings in TypeScript represent textual data. They can be declared using single quotes, double quotes, or backticks. Template literals (backticks) allow embedding expressions directly within the string using the ${} syntax, which is useful for creating dynamic strings without concatenation. This feature improves readability and reduces errors in constructing long strings. With template literals, developers can easily include variables, mathematical expressions, or even function calls inside the string output.

<script>
  let firstName: string = "John";
  let lastName: string = "Doe";
  let greeting: string = `Hello, ${firstName} ${lastName}!`;
  console.log(greeting);
</script>
      

2.4 Numbers and arithmetic operations

Numbers in TypeScript represent both integers and floating-point values. TypeScript follows JavaScript’s number system, which is based on 64-bit floating-point precision. You can perform arithmetic operations such as addition, subtraction, multiplication, and division with numbers. It also supports mathematical functions like rounding and trigonometry via the Math library. By declaring numbers with the number type, TypeScript ensures that mathematical operations are used correctly and that inappropriate operations like adding a string to a number are caught at compile time.

<script>
  let x: number = 10;
  let y: number = 5;
  let result: number = x * y + 2;
  console.log(result);
</script>
      

2.5 Booleans

The boolean type in TypeScript represents truth values, either true or false. It is often used in conditional checks, flags, and control flow logic. By strictly typing values as booleans, TypeScript prevents assigning numbers or strings to variables that are meant to be logical indicators. This enhances code reliability, especially when working with decision-making structures like if statements or loops. Using booleans also improves readability by making the intent of variables clear.

<script>
  let isLoggedIn: boolean = true;
  if (isLoggedIn) {
    console.log("Welcome back!");
  } else {
    console.log("Please log in.");
  }
</script>
      

2.6 Arrays and array operations

Arrays in TypeScript hold collections of values of the same type. They can be defined using type[] syntax or the generic Array<type> syntax. TypeScript enforces type consistency across array elements, which helps avoid runtime errors. Arrays support common operations such as iteration, filtering, mapping, and reducing. By typing arrays properly, developers can leverage auto-completion in IDEs and get compile-time checks on operations, making data manipulation safer and more predictable.

<script>
  let numbers: number[] = [1, 2, 3, 4];
  numbers.push(5);
  let doubled = numbers.map(n => n * 2);
  console.log(doubled);
</script>
      

2.7 Tuples

Tuples in TypeScript are fixed-length arrays with specific types assigned to each element. Unlike regular arrays, tuples define both the number of elements and the type of each position. This makes them useful for representing structured data, such as a coordinate pair or a return value with multiple types. Tuples increase code clarity because the meaning of each position is fixed and known at compile time, preventing misuses of array elements in unintended ways.

<script>
  let person: [string, number] = ["Alice", 25];
  console.log(`Name: ${person[0]}, Age: ${person[1]}`);
</script>
      

2.8 Enums

Enums in TypeScript allow you to define a set of named constants, either numeric or string-based. They make code more readable and self-documenting by replacing magic numbers or string literals with meaningful names. Enums can be used to represent states, roles, directions, or any group of related values. They can also be reverse-mapped when using numeric enums, providing flexibility in usage. Enums improve maintainability by centralizing the definition of values used across the application.

<script>
  enum Direction { Up, Down, Left, Right }
  let move: Direction = Direction.Up;
  console.log(move);
</script>
      

2.9 Any type

The any type disables TypeScript’s type checking, allowing a variable to hold values of any type. While it provides flexibility, it removes the benefits of static typing, so it should be used cautiously. The any type is typically used when migrating JavaScript code to TypeScript or when the type of data cannot be known ahead of time. Overusing any can lead to errors slipping through, so it’s best replaced with stricter types whenever possible.

<script>
  let randomValue: any = 10;
  randomValue = "hello";
  randomValue = true;
  console.log(randomValue);
</script>
      

2.10 Unknown type

The unknown type is safer than any because it forces type checking before use. A variable with type unknown can hold any value, but you cannot perform operations on it until its type is narrowed through type guards. This encourages writing safer code by validating the type of a variable before accessing its properties or methods. It is often used when dealing with external input or APIs where the type may vary.

<script>
  let data: unknown = "hello";
  if (typeof data === "string") {
    console.log(data.toUpperCase());
  }
</script>
      

2.11 Void type

The void type in TypeScript is primarily used for functions that do not return a value. It explicitly tells the compiler that the function’s purpose is to perform an action rather than compute and return data. Attempting to use a return value from a void function results in a compile-time error. Void is rarely used for variables because assigning values to them does not make sense. It emphasizes intent in function design.

<script>
  function logMessage(message: string): void {
    console.log(message);
  }
  logMessage("Hello, World!");
</script>
      

2.12 Null and undefined

null and undefined are two special types in TypeScript. undefined is the default value of uninitialized variables, while null is an explicit assignment representing “no value.” TypeScript can be configured with the strictNullChecks option to prevent unintended null or undefined assignments. Handling these values correctly avoids runtime errors such as null reference exceptions. They are often used in optional properties, APIs, or situations where a value may intentionally be absent.

<script>
  let value: string | null = null;
  let notAssigned: undefined = undefined;
  console.log(value, notAssigned);
</script>
      

2.13 Type inference

TypeScript can automatically infer types when a variable is initialized with a value. This means developers don’t always need to annotate types explicitly. For example, when you write let x = 5, TypeScript infers that x is of type number. Type inference makes code cleaner without sacrificing type safety. However, it is recommended to add explicit annotations when the intent is not obvious, especially for function parameters and complex structures.

<script>
  let count = 42; // inferred as number
  let message = "hello"; // inferred as string
  console.log(count, message);
</script>
      

2.14 Type annotations

Type annotations explicitly declare the type of a variable or function. This improves code clarity and ensures that values assigned to a variable match the declared type. Annotations are particularly useful in function signatures where input and output types must be clear. By annotating variables, developers gain stronger guarantees about their code’s correctness, helping to prevent bugs. Type annotations also improve IDE support by providing better auto-completion and error checking.

<script>
  let age: number = 25;
  function greet(name: string): string {
    return `Hello, ${name}`;
  }
  console.log(greet("Sam"));
</script>
      

2.15 Best practices in basic types

Best practices in TypeScript’s basic types include preferring let and const over var, minimizing the use of any, and favoring unknown where the type is uncertain. Always use strict null checking and clear type annotations where inference is ambiguous. Employ enums and tuples when appropriate to make data structures more descriptive. By adhering to these practices, developers can write code that is easier to understand, safer to maintain, and less prone to errors.

<script>
  const appName: string = "MyApp";
  let version: number = 1.0;
  let isStable: boolean = true;
  console.log(`${appName} v${version} stable: ${isStable}`);
</script>
      

3.1 Function declaration:

Function declarations in TypeScript define a named function using the function keyword followed by a name, parameters, and a block of code. These functions are hoisted, meaning they can be called before they appear in the code. TypeScript also allows specifying parameter and return types to enforce type safety.

// Function declaration with type annotations
function greet(name: string): string {
  return "Hello, " + name;
}

console.log(greet("Majid")); // Output: Hello, Majid
      
3.2 Function expressions:

Function expressions involve assigning a function to a variable. Unlike declarations, they are not hoisted. Function expressions can be anonymous or named and allow TypeScript to infer or explicitly define the type. They are flexible and commonly used in callbacks or when you need a function as a value.

// Function expression assigned to a variable
const multiply = function(a: number, b: number): number {
  return a * b;
}

console.log(multiply(5, 3)); // Output: 15
      
3.3 Arrow functions:

Arrow functions provide a concise syntax for writing functions. They automatically bind the this context from the surrounding scope, making them ideal for callbacks and event handlers. TypeScript supports type annotations for parameters and return values. They are especially useful for inline functions.

// Arrow function with type annotations
const add = (x: number, y: number): number => x + y;

console.log(add(10, 20)); // Output: 30
      
3.4 Optional parameters:

In TypeScript, you can define parameters as optional by adding a question mark ? after the parameter name. Optional parameters must come after required ones. If no value is provided, they are undefined. This allows flexible function calls without needing multiple overloads.

function fullName(firstName: string, lastName?: string): string {
  return lastName ? firstName + " " + lastName : firstName;
}

console.log(fullName("Majid"));       // Output: Majid
console.log(fullName("Majid", "F"));  // Output: Majid F
      
3.5 Default parameters:

Default parameters allow functions to assign a default value to a parameter if no argument is provided. This avoids undefined values and makes function calls simpler. TypeScript enforces type safety even for default values.

function greetUser(name: string = "Guest"): string {
  return "Hello, " + name;
}

console.log(greetUser());       // Output: Hello, Guest
console.log(greetUser("Majid")); // Output: Hello, Majid
      
3.6 Rest parameters:

Rest parameters allow a function to accept an indefinite number of arguments as an array. The syntax uses three dots .... TypeScript supports typing these parameters as arrays of specific types, enabling type-safe aggregation of multiple inputs.

function sumAll(...numbers: number[]): number {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sumAll(1, 2, 3, 4)); // Output: 10
      
3.7 Function overloading:

Function overloading allows you to define multiple function signatures with different parameter types. TypeScript resolves the correct function implementation based on the argument types. Only one implementation function is provided, with multiple declared signatures for type checking.

function display(value: string): string;
function display(value: number): string;
function display(value: any): string {
  return "Value: " + value;
}

console.log(display("Majid")); // Output: Value: Majid
console.log(display(25));      // Output: Value: 25
      
3.8 Return types:

TypeScript allows specifying the return type of a function explicitly. This ensures that the function returns the expected type, helping catch errors at compile-time. Functions without a return value use void as the return type.

function square(num: number): number {
  return num * num;
}

console.log(square(5)); // Output: 25
      
3.9 Anonymous functions:

Anonymous functions are functions without a name. They are usually used as arguments to other functions, like callbacks. In TypeScript, you can type parameters and return types even for anonymous functions.

setTimeout(function() {
  console.log("This is an anonymous function!");
}, 1000);
      
3.10 Callbacks in TypeScript:

Callbacks are functions passed as arguments to other functions. They allow asynchronous behavior and event handling. TypeScript can type both the argument and return type of callbacks for safer code execution.

function greetUser(name: string, callback: (msg: string) => void) {
  callback("Hello, " + name);
}

greetUser("Majid", (message) => console.log(message));
// Output: Hello, Majid
      
3.11 Higher-order functions:

Higher-order functions either take functions as parameters, return functions, or both. They allow functional programming patterns like map, filter, and reduce. TypeScript can define precise function types to ensure type safety.

function multiplyBy(factor: number) {
  return (num: number) => num * factor;
}

const double = multiplyBy(2);
console.log(double(5)); // Output: 10
      
3.12 Function type aliases:

Type aliases allow defining reusable function types. They simplify type annotations and improve readability. TypeScript lets you specify parameter and return types in aliases for consistent function signatures across code.

type MathOperation = (x: number, y: number) => number;

const add: MathOperation = (a, b) => a + b;
console.log(add(3, 7)); // Output: 10
      
3.13 Generic functions:

Generic functions work with multiple types while preserving type safety. Using type parameters, you can write reusable functions without losing type checking. This avoids casting or using any, keeping code flexible and safe.

function identity(value: T): T {
  return value;
}

console.log(identity("Hello")); // Output: Hello
console.log(identity(123));     // Output: 123
      
3.14 Async functions:

Async functions simplify asynchronous operations using the async keyword. They always return a Promise. Await can be used inside async functions to pause execution until the Promise resolves, making asynchronous code easier to read and maintain.

async function fetchData() {
  return "Data fetched!";
}

fetchData().then(data => console.log(data)); // Output: Data fetched!
      
3.15 Best practices in function design:

Writing clean and reusable functions is essential. Keep functions small, single-responsibility, type-safe, and use meaningful names. Avoid side effects, prefer pure functions, and leverage TypeScript features like optional/default parameters, generics, and type aliases for maintainable code.

// Example of clean function design
function calculateArea(width: number, height: number): number {
  return width * height;
}

console.log(calculateArea(5, 10)); // Output: 50
      

4.1 Introduction to OOP

Object-Oriented Programming (OOP) is a paradigm that organizes code into objects containing properties and methods, making software more modular and maintainable. TypeScript supports OOP concepts such as classes, inheritance, encapsulation, and polymorphism. These allow developers to design applications using real-world models, improve readability, and reduce duplication. By using OOP in TypeScript, developers can write scalable and structured code that is easier to debug, test, and extend for large projects.

<script>
class Animal {
  speak() {
    console.log("This animal makes a sound.");
  }
}
const pet = new Animal();
pet.speak(); // Output: This animal makes a sound.
</script>
4.2 Classes in TypeScript

Classes are blueprints for creating objects. They define properties and methods for objects. TypeScript adds static typing to classes, ensuring properties and methods have defined types. Classes are central to OOP as they support inheritance, encapsulation, and method overriding. Developers can instantiate multiple objects from a class and extend it to create new functionality without rewriting existing code, promoting reusability and structured design.

<script>
class Car {
  brand: string;

  constructor(brand: string) {
    this.brand = brand;
  }

  drive() {
    console.log(`${this.brand} is driving.`);
  }
}
const myCar = new Car("Toyota");
myCar.drive(); // Output: Toyota is driving.
</script>
4.3 Properties and fields

Properties (fields) are variables defined in a class that store data specific to objects. They can have types, be initialized directly, or through the constructor. TypeScript allows access modifiers on properties to enforce encapsulation. Fields allow objects to maintain their state, making classes flexible and predictable. Properly defined fields improve code safety and clarity, and prevent unintended changes to important data.

<script>
class Student {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
const student = new Student("Alice", 20);
console.log(student.name); // Output: Alice
</script>
4.4 Constructor functions

Constructors are special methods in a class that run when a new object is created. They initialize properties and can validate or process input data. TypeScript allows constructors to enforce types, ensuring reliable object creation. By using constructors, developers can set up default states, dependencies, or configurations necessary for the object, making instantiation predictable and controlled.

<script>
class Book {
  title: string;
  author: string;

  constructor(title: string, author: string) {
    this.title = title;
    this.author = author;
  }
}
const book = new Book("1984", "George Orwell");
console.log(book.title); // Output: 1984
</script>
4.5 Methods

Methods are functions defined in a class that describe object behavior. They can read and modify properties, accept parameters, and return values. Methods allow interaction with objects while encapsulating functionality. TypeScript provides typing for method parameters and return values, preventing errors during development. Using methods ensures consistent behavior across objects and promotes code reuse.

<script>
class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }
}
const calc = new Calculator();
console.log(calc.add(5, 3)); // Output: 8
</script>
4.6 Access modifiers (public, private, protected)

Access modifiers control visibility of class members. Public members are accessible anywhere, private only within the class, and protected within the class and subclasses. Modifiers enforce encapsulation, hide implementation details, and prevent external misuse. Using modifiers ensures better control over data integrity and protects class internals while exposing only necessary interfaces.

<script>
class BankAccount {
  private balance: number;

  constructor(initial: number) {
    this.balance = initial;
  }

  getBalance(): number {
    return this.balance;
  }
}
const account = new BankAccount(1000);
console.log(account.getBalance()); // Output: 1000
</script>
4.7 Readonly properties

Readonly properties cannot be reassigned after initialization. They help maintain immutability for values like IDs or constants. TypeScript enforces this at compile time, preventing accidental changes and improving reliability. Readonly ensures that important data remains consistent throughout the object's lifecycle.

<script>
class Person {
  readonly id: number;

  constructor(id: number) {
    this.id = id;
  }
}
const person = new Person(101);
console.log(person.id); // Output: 101
// person.id = 200; // Error
</script>
4.8 Getters and setters

Getters and setters allow controlled access to properties. Getters retrieve values, setters modify them with validation if necessary. This encapsulation protects internal state while providing an interface for external code. TypeScript supports typed getters and setters, ensuring correct usage. Using them improves maintainability and prevents invalid state changes.

<script>
class Rectangle {
  private _width: number;
  private _height: number;

  constructor(width: number, height: number) {
    this._width = width;
    this._height = height;
  }

  get area(): number {
    return this._width * this._height;
  }

  set width(value: number) {
    if(value > 0) this._width = value;
  }
}
const rect = new Rectangle(10, 5);
console.log(rect.area); // Output: 50
</script>
4.9 Static members

Static members belong to the class itself, not individual instances. They are shared across all objects and can be accessed without creating an instance. Static members are often used for utility functions, constants, or counters. TypeScript enforces typing on static members and ensures they are properly accessed via the class name.

<script>
class MathUtil {
  static PI: number = 3.14159;
  static circleArea(radius: number) {
    return MathUtil.PI * radius * radius;
  }
}
console.log(MathUtil.circleArea(5)); // Output: 78.53975
</script>
4.10 Inheritance

Inheritance allows a class (child) to derive properties and methods from another class (parent). It promotes code reuse and hierarchical design. TypeScript uses the extends keyword to enable inheritance, supporting method overriding. Proper inheritance structure reduces code duplication and allows creating specialized classes with additional or modified functionality.

<script>
class Vehicle {
  move() { console.log("Vehicle is moving."); }
}
class Bike extends Vehicle {
  move() { console.log("Bike is pedaling."); }
}
const myBike = new Bike();
myBike.move(); // Output: Bike is pedaling.
</script>
4.11 Polymorphism basics

Polymorphism allows objects of different classes to be treated as instances of a parent class, using shared interfaces or methods. TypeScript supports polymorphism through inheritance and method overriding. This provides flexibility in code, allowing functions to operate on objects of multiple types while maintaining consistent behavior. It is fundamental to OOP for extensible and maintainable design.

<script>
class Animal {
  speak() { console.log("Animal speaks"); }
}
class Dog extends Animal {
  speak() { console.log("Dog barks"); }
}
const pet: Animal = new Dog();
pet.speak(); // Output: Dog barks
</script>
4.12 Abstract classes

Abstract classes cannot be instantiated directly and serve as blueprints for subclasses. They can define abstract methods without implementation that must be implemented in derived classes. TypeScript enforces this at compile time. Abstract classes provide a base structure and ensure consistent interfaces across subclasses, useful for large-scale applications where multiple objects share common behavior.

<script>
abstract class Shape {
  abstract area(): number;
}
class Square extends Shape {
  side: number;
  constructor(side: number) { super(); this.side = side; }
  area(): number { return this.side * this.side; }
}
const sq = new Square(4);
console.log(sq.area()); // Output: 16
</script>
4.13 Interfaces

Interfaces define contracts for classes, specifying properties and methods they must implement. They enable TypeScript to enforce structure without code duplication. Interfaces can be implemented by multiple classes, promoting consistent behavior. This mechanism improves type safety, maintainability, and flexibility. Developers can also extend interfaces to combine multiple behaviors.

<script>
interface Logger {
  log(message: string): void;
}
class ConsoleLogger implements Logger {
  log(message: string) { console.log(message); }
}
const logger = new ConsoleLogger();
logger.log("Hello Interface"); // Output: Hello Interface
</script>
4.14 Implementing multiple interfaces

TypeScript allows classes to implement multiple interfaces, enabling combination of behaviors from different sources. This approach promotes flexibility and decouples code dependencies. Each interface contract must be fulfilled, ensuring consistent implementation. Multiple interfaces help in designing modular, reusable, and scalable applications while maintaining strong type checking and clarity in class responsibilities.

<script>
interface CanFly { fly(): void; }
interface CanSwim { swim(): void; }
class Duck implements CanFly, CanSwim {
  fly() { console.log("Duck is flying"); }
  swim() { console.log("Duck is swimming"); }
}
const d = new Duck();
d.fly(); // Output: Duck is flying
d.swim(); // Output: Duck is swimming
</script>
4.15 OOP best practices

Best practices for OOP in TypeScript include: using classes for modularity, favoring composition over inheritance, keeping methods small and focused, enforcing access modifiers, using interfaces for contracts, and preferring immutability for critical data. Properly structuring classes, applying design patterns, and maintaining consistent naming conventions enhance readability and maintainability. Testing, documentation, and type safety also ensure robust and scalable object-oriented applications.

<script>
// Example: Composition over inheritance
class Engine { start() { console.log("Engine started"); } }
class Car {
  engine: Engine;
  constructor(engine: Engine) { this.engine = engine; }
  startCar() { this.engine.start(); }
}
const engine = new Engine();
const car = new Car(engine);
car.startCar(); // Output: Engine started
</script>

5.1 Union Types

Union types allow a variable to hold more than one type. This is useful when a value can be either one type or another. By using the `|` operator, you can define a union type. It provides flexibility while keeping TypeScript's type safety. Union types are often used in function parameters and return types when multiple types are valid.

let value: string | number;
value = 42;        // valid
value = "Hello";   // also valid
// value = true;   // error: boolean not allowed
      
5.2 Intersection Types

Intersection types combine multiple types into one. A variable of an intersection type must satisfy all included types. This is useful for merging multiple interfaces or object types. Intersection types use the `&` operator. They are especially powerful in scenarios where you need to compose complex objects while ensuring all required properties exist.

interface Person {
  name: string;
}
interface Employee {
  employeeId: number;
}
type Worker = Person & Employee;
const worker: Worker = { name: "Alice", employeeId: 123 };
      
5.3 Literal Types

Literal types allow a variable to have an exact value, not just a type. They are helpful when you want to restrict variables to specific string, number, or boolean values. Literal types are commonly combined with union types to define a set of allowed values. This increases type safety and prevents invalid values.

let direction: "up" | "down" | "left" | "right";
direction = "up";    // valid
// direction = "forward"; // error
      
5.4 Type Aliases

Type aliases allow you to create a new name for a type. They are useful for simplifying complex types or creating reusable type definitions. Type aliases can include primitive types, unions, intersections, or even function signatures. This helps make your code more readable and maintainable.

type ID = string | number;
let userId: ID;
userId = 101;    // valid
userId = "A202"; // also valid
      
5.5 Type Guards

Type guards are runtime checks that allow TypeScript to narrow down a variable’s type. They are essential when working with union types or unknown types. By performing specific checks, TypeScript can infer a more precise type within a block of code, enhancing safety and reducing errors.

function printValue(val: string | number) {
  if (typeof val === "string") {
    console.log("String: " + val);
  } else {
    console.log("Number: " + val);
  }
}
      
5.6 typeof Type Guards

The `typeof` operator is a common type guard for primitives like string, number, or boolean. Using `typeof` checks at runtime, TypeScript can safely narrow down the type within the guarded block. This allows correct operations depending on the variable type.

function process(value: string | number) {
  if (typeof value === "number") {
    console.log(value * 2);
  } else {
    console.log(value.toUpperCase());
  }
}
      
5.7 instanceof Type Guards

The `instanceof` operator is a type guard for class instances. It allows TypeScript to narrow down a variable’s type to a specific class. This is helpful when working with objects from multiple classes or inheritance hierarchies.

class Dog { bark() {} }
class Cat { meow() {} }

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}
      
5.8 Custom Type Guards

Custom type guards are user-defined functions that determine the type of a value. They return a boolean and use the `value is Type` syntax. This allows TypeScript to narrow the type based on more complex conditions than `typeof` or `instanceof`.

interface Fish { swim(): void }
interface Bird { fly(): void }

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

const pet: Fish | Bird = { swim() {} };
if (isFish(pet)) pet.swim();
      
5.9 Nullable Types

Nullable types allow variables to be either a specific type or null/undefined. This is useful for optional values or representing absence of data. TypeScript provides strict checks to prevent null-related runtime errors when using nullable types.

let name: string | null = null;
name = "Alice"; // valid
// console.log(name.length); // error if name is null
      
5.10 Non-null Assertion Operator

The non-null assertion operator `!` tells TypeScript that a value is not null or undefined. It bypasses strict null checks but should be used carefully because incorrect assumptions can cause runtime errors.

let element = document.getElementById("app")!;
element.innerHTML = "Hello World"; // asserts element is not null
      
5.11 Type Assertion

Type assertion lets you override TypeScript’s inferred type. You can assert a variable as a specific type when you know more than the compiler. This is commonly used when working with DOM elements or external data.

let someValue: any = "Hello TypeScript";
let strLength: number = (someValue as string).length;
      
5.12 keyof Operator

The `keyof` operator returns a union of keys of an object type. It is useful when creating generic functions that work with object properties while ensuring type safety.

interface Person { name: string; age: number }
type PersonKeys = keyof Person; // "name" | "age"
      
5.13 Conditional Types

Conditional types allow creating types based on a condition. They are like ternary operators for types, enabling dynamic type computation based on other types. This is very useful in generics for flexible and reusable type definitions.

type Check = T extends string ? "Yes" : "No";
type Result = Check; // "No"
type Result2 = Check; // "Yes"
      
5.14 Mapped Types

Mapped types let you transform existing types into new types by iterating over properties. They are helpful for creating readonly, optional, or modified versions of an interface without duplicating code.

type Person = { name: string; age: number };
type ReadonlyPerson = { readonly [K in keyof Person]: Person[K] };
      
5.15 Utility Types Overview

TypeScript provides built-in utility types for common patterns. Examples include `Partial`, `Required`, `Readonly`, `Pick`, and `Omit`. They simplify type transformations and reduce boilerplate code, making type management more efficient.

interface Todo { title: string; completed: boolean }
type PartialTodo = Partial; // all properties optional
type ReadonlyTodo = Readonly; // all properties readonly
      

6.1 What are Interfaces
Interfaces in TypeScript define **contracts for objects**, classes, or functions. They describe the **shape of data**, including property names, types, and method signatures, without providing implementation. Interfaces ensure that objects or classes adhere to a consistent structure, helping with type safety, code readability, and maintainability in large applications.
// Example: Basic interface
interface Person {
  name: string;
  age: number;
}

const user: Person = { name: "Ali", age: 30 };
console.log(user.name, user.age);
      
6.2 Optional Properties
Interfaces can define optional properties using a **question mark (`?`)**. Optional properties allow flexibility by letting objects omit certain fields while still conforming to the interface. This is useful in APIs, partial updates, or optional configuration objects.
interface Person {
  name: string;
  age?: number; // optional
}

const user1: Person = { name: "Ali" };
const user2: Person = { name: "Sara", age: 25 };
      
6.3 Readonly Properties in Interfaces
Properties marked as `readonly` cannot be reassigned after initialization. This ensures immutability and prevents accidental changes to critical data structures. Readonly properties are ideal for IDs, constants, or configuration objects.
interface User {
  readonly id: number;
  name: string;
}

const u: User = { id: 1, name: "Ali" };
// u.id = 2; // Error: Cannot assign to 'id'
      
6.4 Function Types in Interfaces
Interfaces can describe **function types**, specifying parameters and return types. This is useful for defining callbacks, APIs, or reusable function structures, ensuring type-safe usage.
interface Add {
  (a: number, b: number): number;
}

const sum: Add = (x, y) => x + y;
console.log(sum(5, 10));
      
6.5 Indexable Types
Interfaces can define **indexable types**, allowing objects to have dynamic keys with specific value types. Useful for dictionaries, maps, or flexible objects while retaining type safety.
interface StringArray {
  [index: number]: string;
}

const fruits: StringArray = ["Apple", "Banana"];
console.log(fruits[0]);
      
6.6 Extending Interfaces
Interfaces can extend other interfaces to inherit properties, promoting reusability and modular design. This allows combining multiple contracts into one cohesive structure.
interface Person {
  name: string;
}

interface Employee extends Person {
  employeeId: number;
}

const emp: Employee = { name: "Ali", employeeId: 101 };
      
6.7 Implementing Interfaces in Classes
Classes can implement interfaces to **enforce the structure** at compile time. This ensures that classes provide all properties and methods defined in the interface.
interface Person {
  name: string;
  greet(): void;
}

class User implements Person {
  constructor(public name: string) {}
  greet() { console.log(`Hello, ${this.name}`); }
}

const u = new User("Ali");
u.greet();
      
6.8 Hybrid Types
Hybrid types combine **properties and callable signatures** in one interface. Useful for objects that are both functions and have additional properties or methods.
interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

function getCounter(): Counter {
  let counter = function(start: number) { return `Count: ${start}`; };
  counter.interval = 123;
  counter.reset = () => { console.log("Reset"); };
  return counter;
}

const c = getCounter();
console.log(c(10));
c.reset();
      
6.9 Interfaces vs Types
Interfaces are primarily used for **object structures**, while type aliases can define **unions, primitives, or tuples**. Interfaces support extension and declaration merging, making them more flexible for large-scale code, whereas types are better for complex or combined types.
interface A { name: string; }
type B = { age: number; }

const obj: A & B = { name: "Ali", age: 30 };
      
6.10 Interface Merging
TypeScript allows **merging multiple declarations** of the same interface into one. This is useful when adding properties across modules without breaking existing code.
interface User { name: string; }
interface User { age: number; }

const u: User = { name: "Ali", age: 25 };
      
6.11 Generics in Interfaces
Interfaces can be **generic**, allowing reusable and type-safe definitions. Generics provide flexibility without sacrificing type checking.
interface Box {
  content: T;
}

const stringBox: Box = { content: "Hello" };
const numberBox: Box = { content: 123 };
      
6.12 Polymorphic Interfaces
Polymorphic interfaces use **generics to allow multiple types**, enabling the same interface to work with different data types. This enhances code reusability and flexibility.
interface Response {
  data: T;
  success: boolean;
}

const res: Response = { data: "OK", success: true };
const res2: Response = { data: 200, success: true };
      
6.13 Best Practices with Interfaces
Use interfaces for **object shapes and class contracts**. Prefer extension over duplication, use optional and readonly properties wisely, and combine with generics for reusable structures. Keep them simple and well-documented.
// Example: clean interface
interface Config { readonly version: string; optional?: boolean; }
      
6.14 Real-World Examples
Interfaces are widely used in **API responses, class contracts, and component props** in frameworks like Angular or React. They provide compile-time safety and consistency across projects.
interface ApiResponse {
  data: T;
  status: number;
}

const response: ApiResponse = { data: "Success", status: 200 };
      
6.15 Debugging Interface Errors
Common interface errors include missing properties, type mismatches, or incorrect implementations in classes. Use compiler messages and type hints to locate and fix issues. Proper design of interfaces prevents most runtime errors.
interface User { name: string; age: number; }
const u: User = { name: "Ali" }; // Error: missing age
      

7.1 Introduction to Generics
Generics allow you to write flexible, reusable code by defining functions, classes, or interfaces with placeholder types. These placeholders, typically written as <T>, are replaced with actual types when the code is used. Generics maintain type safety while reducing duplication, making code easier to maintain and scalable. They are particularly useful for collections, utility functions, and APIs that need to handle multiple data types.
// Generic function example
function identity(arg: T): T {
    return arg;
}
let output = identity("Hello World");
console.log(output); // "Hello World"
      
7.2 Generic Functions
Generic functions allow you to define a function that works with any type, while still preserving type safety. You define a type parameter inside angle brackets <T> before the function arguments. This enables the function to adapt dynamically to the type of input it receives. It is especially useful for utility functions like mapping, filtering, or wrapping data.
function wrapInArray(value: T): T[] {
    return [value];
}
const numbers = wrapInArray(5); // [5]
const strings = wrapInArray("test"); // ["test"]
      
7.3 Generic Classes
Generic classes allow you to create classes that can operate with different types without losing type safety. You define a type parameter after the class name. This is useful when building data structures like stacks, queues, or linked lists that can hold any type of data. Generics make the class reusable and maintain strong typing.
class Box {
    contents: T;
    constructor(value: T) {
        this.contents = value;
    }
    getContents(): T {
        return this.contents;
    }
}
const numberBox = new Box(123);
console.log(numberBox.getContents()); // 123
      
7.4 Generic Interfaces
Generic interfaces define a contract with placeholder types, allowing objects or classes to implement flexible structures. By using generics, you can enforce type safety while keeping interfaces adaptable. This is useful when designing APIs or libraries that need to work with multiple data types without rewriting the interface.
interface KeyValue {
    key: K;
    value: V;
}
const kv: KeyValue = { key: "age", value: 30 };
console.log(kv); // { key: "age", value: 30 }
      
7.5 Constraints with Generics
Constraints allow you to limit the types a generic can accept. Using the extends keyword, you can enforce that the type parameter meets certain criteria, like having specific properties or being a certain interface. This prevents errors while still allowing flexibility.
interface Lengthwise { length: number; }
function logLength(arg: T): void {
    console.log(arg.length);
}
logLength([1,2,3]); // 3
logLength("hello");  // 5
      
7.6 Default Types
Default types allow generics to assume a type when none is provided. This simplifies code and reduces the need for explicit type annotations while maintaining type safety. It’s particularly useful in libraries where most users will work with a common type.
function createArray(length: number, value: T): T[] {
    return Array(length).fill(value);
}
console.log(createArray(3, "a")); // ["a","a","a"]
      
7.7 Generic Utility Types
TypeScript provides built-in utility types like Partial<T>, Readonly<T>, and Record<K,T> that use generics. These help transform existing types into new forms while keeping type safety, which is useful for creating flexible APIs or dynamic data models.
interface User { id: number; name: string; }
const partialUser: Partial = { name: "Alice" };
console.log(partialUser); // { name: "Alice" }
      
7.8 Multiple Generic Parameters
Generics can take multiple type parameters, making functions, classes, or interfaces even more flexible. Each parameter can represent a different type, allowing complex type-safe structures without duplication. This is helpful when modeling relationships between multiple types.
function merge(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}
const merged = merge({name:"Alice"}, {age:30});
console.log(merged); // { name: "Alice", age: 30 }
      
7.9 Bounded Generics
Bounded generics restrict the generic type to a specific subset of types, using the extends keyword. This allows functions or classes to safely access certain properties or methods, while keeping the flexibility of generics. It's useful when building utility functions or APIs that must enforce type characteristics.
function getProperty(obj: T, key: K) {
    return obj[key];
}
const person = { name: "Bob", age: 25 };
console.log(getProperty(person, "name")); // "Bob"
      
7.10 Using keyof with Generics
The keyof operator can be combined with generics to restrict keys to valid properties of an object. This ensures type safety when accessing properties dynamically and prevents runtime errors. It's widely used in utility functions and dynamic data manipulation.
function pluck(obj: T, keys: K[]): T[K][] {
    return keys.map(k => obj[k]);
}
const personObj = { name:"Alice", age:25 };
console.log(pluck(personObj, ["name"])); // ["Alice"]
      
7.11 Conditional Generics
Conditional generics use type conditions (extends ? :) to select one type or another depending on the input type. This allows advanced type computations and helps create APIs that behave differently based on input types, all while keeping type safety.
type Message = T extends string ? "Text message" : "Other message";
let msg1: Message; // "Text message"
let msg2: Message; // "Other message"
      
7.12 Mapped Generics
Mapped types allow you to transform properties of an existing type using generics. You can iterate over keys of a type to create new types dynamically. This is useful for creating utility types or converting interfaces without duplicating code.
type ReadonlyUser = { readonly [P in keyof T]: T[P] };
const user: ReadonlyUser<{ name: string }> = { name: "Alice" };
// user.name = "Bob"; // Error: cannot assign
      
7.13 Practical Examples
Generics are widely used in collections, APIs, and utility functions. For example, a generic Stack class can store any type of elements, providing methods for push and pop while maintaining type safety. This avoids writing multiple versions of the same data structure for different types.
class Stack {
    private items: T[] = [];
    push(item: T) { this.items.push(item); }
    pop(): T | undefined { return this.items.pop(); }
}
const numberStack = new Stack();
numberStack.push(10);
console.log(numberStack.pop()); // 10
      
7.14 Performance Considerations
Generics in TypeScript exist only at compile time; they do not affect runtime performance because TypeScript compiles to plain JavaScript. Therefore, generics provide type safety without any runtime overhead. However, overusing complex generics can make the code harder to read or maintain.
// Generics don't affect runtime
function echo(value: T): T { return value; }
console.log(echo(123)); // 123
      
7.15 Best Practices
Use clear and meaningful generic names, typically T for type, K for key, and V for value. Avoid overly complex generic chains, prefer default types when appropriate, and use constraints to maintain safety. Always document generic usage to make code readable and maintainable.
function firstElement(arr: T[]): T | undefined {
    return arr[0];
}
console.log(firstElement([1,2,3])); // 1
      

8.1 Introduction to Modules
Modules in TypeScript allow you to split your code into separate files, each with its own scope, to improve organization and maintainability. Each module can export variables, functions, or classes, and import members from other modules. This promotes code reuse and prevents global namespace pollution. Modules can be internal or external, with external modules usually using ES6 import/export syntax, making it easier to manage dependencies and build scalable applications.
// example of a simple module: mathUtils.ts
export const pi = 3.14;
export function square(x: number): number {
    return x * x;
}
      
8.2 Exporting Variables
Variables can be exported from a module so that they can be accessed in other files. Using the export keyword, you make variables public to other modules. This helps maintain encapsulation while allowing selective sharing of data or configuration constants.
export const appName = "TypeScriptApp";
export let version = 1.0;
      
8.3 Exporting Functions
Functions can be exported to allow other modules to use them. This is helpful for utility functions or shared logic that should not be duplicated across files. Functions can be named exports, making multiple functions available from a single module.
export function greet(name: string): string {
    return `Hello, ${name}`;
}
export function sum(a: number, b: number): number {
    return a + b;
}
      
8.4 Exporting Classes
Classes can be exported to allow object-oriented patterns across multiple modules. Exported classes can be instantiated or extended in other modules, providing flexibility and reusable components.
export class Person {
    constructor(public name: string, public age: number) {}
    describe(): string {
        return `${this.name} is ${this.age} years old.`;
    }
}
      
8.5 Default Exports
Default exports allow a module to export a single main value, function, or class. When importing, you can give it any name, simplifying usage when a module has one primary export. This is commonly used for libraries or components.
// default export example
export default function greetDefault(name: string) {
    return `Hi, ${name}`;
}
// importing in another file
import greetFn from './greetDefault';
console.log(greetFn("Alice")); // "Hi, Alice"
      
8.6 Importing Modules
Modules can import members using import { member } syntax. This allows selective access to variables, functions, or classes from other files while keeping code modular and maintainable. TypeScript resolves imports relative to module paths or via node_modules for libraries.
import { pi, square } from './mathUtils';
console.log(pi); // 3.14
console.log(square(5)); // 25
      
8.7 Renaming Imports
You can rename imports using the as keyword. This is useful to avoid naming conflicts or make imported members more meaningful in the current module context.
import { square as sq } from './mathUtils';
console.log(sq(6)); // 36
      
8.8 Importing All Members
You can import all exported members from a module using the * as syntax. This creates a namespace-like object containing all the module’s exports, useful when multiple members are needed.
import * as MathUtils from './mathUtils';
console.log(MathUtils.pi); // 3.14
console.log(MathUtils.square(4)); // 16
      
8.9 Re-exporting
Re-exporting allows a module to export members it imported from another module. This is useful for creating aggregated modules, centralizing exports for easier imports in other parts of the application.
export { pi, square } from './mathUtils';
      
8.10 Namespaces Overview
Namespaces (formerly called internal modules) provide a way to organize code within a single file or across multiple files using a common namespace identifier. Namespaces help prevent global namespace pollution and allow grouping of related variables, functions, or classes. While ES6 modules are preferred, namespaces are still useful for organizing legacy TypeScript code or large libraries.
namespace Geometry {
    export function circleArea(radius: number): number {
        return Math.PI * radius * radius;
    }
}
console.log(Geometry.circleArea(3)); // 28.27
      
8.11 Nested Namespaces
Namespaces can be nested to create hierarchical structures, grouping related functionality logically. Each nested namespace can export its own members, and outer namespaces provide a container to avoid collisions with other code.
namespace Geometry {
    export namespace Shapes {
        export function squareArea(side: number): number {
            return side * side;
        }
    }
}
console.log(Geometry.Shapes.squareArea(4)); // 16
      
8.12 Declaration Merging in Namespaces
TypeScript allows declaration merging, where multiple namespace declarations with the same name are combined. This is useful for extending modules or libraries without modifying original code, supporting modular growth.
namespace Library {
    export function read() { console.log("Read"); }
}
namespace Library {
    export function write() { console.log("Write"); }
}
Library.read();  // "Read"
Library.write(); // "Write"
      
8.13 Modules vs Namespaces
Modules and namespaces both organize code but differ in usage. Modules are file-based, using import/export syntax and are standard in modern TypeScript. Namespaces are internal constructs used to group related code in a single file or across multiple files. Modules are recommended for new projects, while namespaces are useful for legacy or library code where modules may not be available.
// Modules are file-based; namespaces are internal
      
8.14 External Module Libraries
TypeScript supports external modules via npm packages or third-party libraries. You can import them using ES6 import syntax, gaining type safety through type definitions (e.g., DefinitelyTyped). This enables integration with popular libraries like lodash, express, or React.
import * as _ from 'lodash';
console.log(_.shuffle([1,2,3])); // [2,1,3] (shuffled array)
      
8.15 Best Practices
Use modules instead of namespaces for modern TypeScript projects. Keep modules small and focused, export only what is necessary, prefer default exports for main functionality, and leverage type definitions for external libraries. For namespaces, avoid over-nesting, and use declaration merging carefully. Proper module and namespace management improves code readability, maintainability, and prevents naming conflicts.
// Example: small focused module
export function logMessage(msg: string) { console.log(msg); }
      

9.1 tsc Compiler Basics
The TypeScript compiler (tsc) transforms TypeScript (.ts) files into plain JavaScript (.js). It performs type checking, ensures syntax correctness, and respects configuration options in tsconfig.json. By running tsc filename.ts, you generate a JavaScript file ready for execution in any JavaScript environment. Understanding the compiler is crucial for debugging, integrating with build tools, and maintaining type-safe applications.
tsc app.ts
// This will generate app.js from app.ts
      
9.2 tsconfig.json Overview
The tsconfig.json file defines the root-level TypeScript project settings. It controls compiler behavior, file inclusions/exclusions, module resolution, and target JavaScript versions. This file enables consistent builds across projects and allows using tsc --project without manually specifying compiler options. Properly configuring it ensures maintainable, type-safe, and optimized projects.
{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
      
9.3 Compiler Options
TypeScript compiler options control code generation, type checking, and strictness. They include settings like strict, noImplicitAny, esModuleInterop, outDir, and more. Choosing the right combination ensures safer, more predictable builds, aligns with team standards, and enables modern JavaScript features while maintaining compatibility with older environments.
// Example compiler options
{
  "strict": true,
  "noImplicitAny": true,
  "esModuleInterop": true
}
      
9.4 Target and Module Options
The target option specifies the JavaScript version (e.g., ES5, ES6) that TypeScript outputs. The module option determines the module system, like CommonJS, AMD, or ESNext. These settings ensure compatibility with browsers or Node.js environments. Selecting the correct target and module allows TypeScript code to run smoothly across platforms.
{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs"
  }
}
      
9.5 Strict Mode
Strict mode enables a set of type-checking rules that catch potential errors early. This includes noImplicitAny, strictNullChecks, and strictFunctionTypes. Using strict mode improves code quality, reduces runtime errors, and enforces better design patterns by requiring explicit type annotations and safe handling of null/undefined values.
{
  "compilerOptions": {
    "strict": true
  }
}
      
9.6 Source Maps
Source maps map the generated JavaScript back to the original TypeScript files. They are essential for debugging because you can see the TypeScript code in browser dev tools instead of the compiled JavaScript. Enabling source maps improves developer productivity and simplifies troubleshooting in complex applications.
{
  "compilerOptions": {
    "sourceMap": true
  }
}
      
9.7 OutDir and RootDir
outDir specifies where compiled JavaScript files are generated, while rootDir defines the root folder of TypeScript source files. Proper use of these options keeps project structure organized and prevents compilation output from mixing with source code. It is critical for large projects or when integrating with build pipelines.
{
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist"
  }
}
      
9.8 Include and Exclude
The include array specifies which files TypeScript should compile, and exclude defines files or folders to ignore, like node_modules. Proper configuration improves compilation performance and ensures that only relevant project files are processed.
{
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
      
9.9 Incremental Compilation
Incremental compilation allows TypeScript to compile only changed files rather than the entire project. This significantly reduces build time for large projects. It is enabled with the incremental option and generates a .tsbuildinfo file to track previous compilations.
{
  "compilerOptions": {
    "incremental": true
  }
}
      
9.10 Watch Mode
Watch mode (tsc --watch) automatically recompiles TypeScript files on change. This provides live feedback during development and speeds up testing cycles. It's especially useful in combination with incremental compilation for large projects.
tsc --watch
      
9.11 Type Checking
TypeScript performs static type checking during compilation to catch errors early. This includes checking for type mismatches, incorrect function signatures, or missing properties. Type checking improves code reliability and reduces runtime bugs, providing developers with a safety net while writing complex applications.
let num: number = "string"; // Error: Type 'string' is not assignable to type 'number'
      
9.12 Errors and Warnings
The compiler reports errors and warnings for issues like type mismatches, missing imports, or unreachable code. Understanding these messages helps fix problems early, maintain code quality, and enforce best practices. Errors prevent JavaScript output, whereas warnings notify potential issues without stopping compilation.
let value: any;
console.log(value.notExist); // Warning or runtime error depending on compiler options
      
9.13 Skipping Type Checks
TypeScript allows skipping type checks in certain cases using skipLibCheck or //@ts-ignore. While sometimes useful for third-party libraries or legacy code, overusing it can lead to hidden bugs. It's generally recommended to maintain type safety whenever possible.
// Example
//@ts-ignore
let ignored: number = "string"; // No compiler error
      
9.14 Path Mapping
Path mapping lets you define custom module paths using paths in tsconfig.json. This simplifies imports, avoids relative path hell, and improves maintainability of large projects. It's especially useful when working with deeply nested folders or shared modules.
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@utils/*": ["src/utils/*"]
    }
  }
}
      
9.15 Compiler Best Practices
Always enable strict mode, incremental compilation, and source maps for development. Use rootDir and outDir to organize project structure, and configure include/exclude carefully. Keep tsconfig.json consistent across environments to avoid unexpected behavior. Following these practices ensures maintainable, efficient, and type-safe TypeScript projects.
{
  "compilerOptions": {
    "strict": true,
    "incremental": true,
    "sourceMap": true,
    "rootDir": "src",
    "outDir": "dist"
  }
}
      

10.1 Function Overloading
Function overloading allows a function to have multiple signatures with different parameter types or counts. It helps define versatile functions that behave differently depending on the input. TypeScript checks which overload matches the call at compile time, ensuring type safety. The implementation must handle all variations correctly.
function combine(a: number, b: number): number;
function combine(a: string, b: string): string;
function combine(a: any, b: any): any {
    return a + b;
}
console.log(combine(1,2));   // 3
console.log(combine("Hi","!")); // "Hi!"
      
10.2 Rest Parameters in Depth
Rest parameters allow a function to accept an indefinite number of arguments as an array. They provide flexibility for functions where the exact number of parameters is unknown. They must always be the last parameter in the function signature and can be combined with other parameters.
function sumAll(...nums: number[]): number {
    return nums.reduce((acc, val) => acc + val, 0);
}
console.log(sumAll(1,2,3,4)); // 10
      
10.3 Spread Operator
The spread operator ... expands an iterable like an array or object into individual elements. It is useful for copying, merging arrays or objects, and passing multiple arguments into a function. It improves code readability and avoids mutation of the original objects.
const arr1 = [1,2];
const arr2 = [3,4];
const merged = [...arr1, ...arr2];
console.log(merged); // [1,2,3,4]
      
10.4 Destructuring in Functions
Destructuring allows extracting values from arrays or objects directly in function parameters. It simplifies code and makes functions more readable. You can provide default values and rename variables during destructuring, enabling cleaner and more expressive parameter handling.
function greet({name, age}: {name: string; age: number}) {
    console.log(`Hello ${name}, age ${age}`);
}
greet({name:"Alice", age:30}); // Hello Alice, age 30
      
10.5 Optional Chaining
Optional chaining ?. allows safely accessing deeply nested object properties. If any part of the chain is null or undefined, it returns undefined instead of throwing an error. It’s extremely useful when dealing with dynamic data or API responses.
const user = { profile: { name: "Bob" } };
console.log(user.profile?.name); // Bob
console.log(user.account?.balance); // undefined
      
10.6 Nullish Coalescing
Nullish coalescing ?? provides a default value only when the left-hand side is null or undefined. Unlike the logical OR ||, it does not consider false, 0, or empty string as nullish. This is useful for safe default values without unintended overrides.
let input = null;
console.log(input ?? "Default"); // Default
      
10.7 Type Narrowing
Type narrowing is the process of refining a variable's type to a more specific type using checks like typeof, instanceof, or custom type guards. This ensures safer operations and reduces runtime errors, especially in union types.
function printId(id: string | number) {
    if (typeof id === "string") console.log(id.toUpperCase());
    else console.log(id + 100);
}
printId("abc"); // ABC
printId(50);    // 150
      
10.8 Index Signatures
Index signatures allow objects to have dynamic keys while enforcing consistent value types. This is useful for dictionaries, maps, or objects with unknown property names but consistent value types.
interface StringMap {
    [key: string]: string;
}
const dict: StringMap = { a: "apple", b: "banana" };
console.log(dict["a"]); // apple
      
10.9 Conditional Operators
Conditional operators like ?: allow inline conditional expressions, returning one of two values based on a boolean condition. They reduce verbose if-else statements and improve code readability.
let age = 18;
let status = age >= 18 ? "Adult" : "Minor";
console.log(status); // Adult
      
10.10 Logical Operators
Logical operators (&&, ||, !) combine or invert boolean expressions. They are essential for decision making and control flow in functions, often combined with conditional statements for complex logic.
const a = true;
const b = false;
console.log(a && b); // false
console.log(a || b); // true
      
10.11 Ternary Operator
The ternary operator is a shorthand for if-else statements. It takes a condition followed by ? the value if true and : the value if false. It’s concise and widely used for simple conditional assignments.
const score = 75;
const grade = score >= 60 ? "Pass" : "Fail";
console.log(grade); // Pass
      
10.12 Tagged Template Literals
Tagged template literals allow custom processing of template strings. You can define a function (tag) that receives template parts and expressions, enabling custom formatting, sanitization, or interpolation.
function tag(strings: TemplateStringsArray, ...values: any[]) {
    return strings[0] + values.join("-");
}
console.log(tag`Hello ${"World"} ${123}`); // Hello World-123
      
10.13 Function Decorators (Experimental)
Decorators allow modifying or enhancing functions, methods, or classes at runtime. In TypeScript, decorators are still experimental and require enabling the experimentalDecorators option. They’re useful for logging, caching, or applying metadata.
function log(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args: any[]) {
        console.log("Called with", args);
        return original.apply(this, args);
    };
}
class Example {
    @log
    sum(a: number, b: number) { return a + b; }
}
new Example().sum(2,3); // Logs: Called with [2,3]
      
10.14 Callback Patterns
Callbacks are functions passed as arguments to other functions, executed later. They enable asynchronous patterns, event handling, or chaining operations. Properly typed callbacks in TypeScript ensure type safety and prevent runtime errors.
function fetchData(callback: (data: string) => void) {
    setTimeout(() => callback("Fetched data"), 1000);
}
fetchData((result) => console.log(result)); // Fetched data
      
10.15 Best Practices for Complex Functions
When writing complex functions, prefer small, reusable helper functions, use proper type annotations, handle optional and null values safely, and document expected behavior. Use TypeScript features like generics, union types, and type guards to improve maintainability and readability.
function processNumbers(nums: (number | null)[]): number[] {
    return nums.filter((n): n is number => n !== null).map(n => n * 2);
}
console.log(processNumbers([1,null,3])); // [2,6]
      

11.1 Introduction to Decorators
Decorators are a **special kind of declaration** in TypeScript that can be attached to classes, methods, properties, or parameters to **modify their behavior**. They are functions that run at runtime and allow developers to **add metadata, implement logging, validation, or other logic** in a declarative way. Decorators are widely used in frameworks like Angular and NestJS to simplify boilerplate code and enhance modularity.
// Example: Simple class decorator
function Logger(constructor: Function) {
  console.log('Class created:', constructor.name);
}

@Logger
class Person {
  constructor(public name: string) {}
}
      
11.2 Class Decorators
Class decorators are functions applied to a **class constructor**. They can observe, modify, or replace class definitions. They are useful for **adding metadata or modifying class behavior** globally. Class decorators are called once when the class is declared, making them ideal for logging, dependency injection, or singleton patterns.
// Example: Class decorator adding property
function addId<T extends {new(...args:any[]):{}}>(constructor:T) {
  return class extends constructor {
    id = Math.random();
  };
}

@addId
class User {
  constructor(public name: string) {}
}

const u = new User("Ali");
console.log(u.id);
      
11.3 Method Decorators
Method decorators are applied to **class methods** and can observe or modify the method behavior. They receive the **target, method name, and descriptor**. Method decorators are commonly used for **logging, measuring performance, or authorization checks** before executing the method.
// Example: Method decorator for logging
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    return original.apply(this, args);
  };
}

class Calculator {
  @log
  add(a: number, b: number) { return a + b; }
}

const calc = new Calculator();
calc.add(5, 10);
      
11.4 Property Decorators
Property decorators are applied to **class properties**. They can observe or modify property metadata, but cannot directly change property values. Common use cases include **validation, default values, or logging property access**.
// Example: Property decorator
function readOnly(target: any, key: string) {
  Object.defineProperty(target, key, { writable: false });
}

class Car {
  @readOnly
  brand = "Toyota";
}

const c = new Car();
// c.brand = "Honda"; // Error: cannot assign
      
11.5 Parameter Decorators
Parameter decorators are applied to **function or method parameters**. They allow attaching **metadata** about the parameter for runtime usage, such as validation or dependency injection. They receive the target, method name, and parameter index.
// Example: Parameter decorator
function logParam(target: any, methodName: string, paramIndex: number) {
  console.log(`Parameter ${paramIndex} of ${methodName} is decorated`);
}

class Greeter {
  greet(@logParam name: string) {
    console.log(`Hello, ${name}`);
  }
}

const g = new Greeter();
g.greet("Ali");
      
11.6 Decorator Factories
Decorator factories are **functions that return a decorator**. They allow **configuring decorators** with parameters. This is useful when a decorator needs dynamic input, like specifying logging levels or validation rules.
// Example: Decorator factory
function logLevel(level: string) {
  return function(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args: any[]) {
      console.log(`[${level}] Calling ${key}`);
      return original.apply(this, args);
    };
  };
}

class Task {
  @logLevel("INFO")
  run() { console.log("Task executed"); }
}

new Task().run();
      
11.7 Metadata Reflection
Metadata reflection is a feature allowing **retrieving type or property information at runtime**. Combined with decorators, it enables powerful capabilities like **automatic validation, dependency injection, or serialization**. TypeScript uses the `reflect-metadata` library for this purpose.
import "reflect-metadata";

function typeInfo(target: any, key: string) {
  const type = Reflect.getMetadata("design:type", target, key);
  console.log(`${key} type: ${type.name}`);
}

class User {
  @typeInfo
  name: string;
  age: number;
}
      
11.8 Experimental Decorator Support
Decorators are currently **experimental in TypeScript**, requiring `"experimentalDecorators": true` in `tsconfig.json`. This enables compiler support, letting developers use decorators safely. Without this flag, decorator syntax will throw errors.
/* tsconfig.json */
{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}
      
11.9 Angular Decorators Overview
Angular relies heavily on decorators like `@Component`, `@NgModule`, and `@Injectable` to define **components, modules, and services**. These decorators configure Angular behavior declaratively and simplify boilerplate code.
// Angular example
@Component({
  selector: 'app-root',
  template: '<h1>Hello Angular</h1>'
})
export class AppComponent {}
      
11.10 NestJS Decorators Overview
NestJS uses decorators such as `@Controller`, `@Get`, and `@Post` to define **routes and endpoints**. They help create structured backend applications with clean, readable code.
// NestJS example
@Controller('users')
export class UserController {
  @Get()
  findAll() { return ['user1','user2']; }
}
      
11.11 Combining Multiple Decorators
Multiple decorators can be applied to the same target. They are executed **bottom-up**, providing layered functionality like logging, validation, and metadata injection.
function decoA(target: any) { console.log("A"); }
function decoB(target: any) { console.log("B"); }

@decoA
@decoB
class Demo {}
// Output: B, then A
      
11.12 Using Decorators for Logging
Logging decorators capture method calls, parameters, and outputs. They help **debug and monitor runtime behavior** without modifying original methods.
function log(target: any, key: string, desc: PropertyDescriptor) {
  const original = desc.value;
  desc.value = function(...args: any[]) {
    console.log(`Calling ${key} with`, args);
    return original.apply(this, args);
  }
}

class MathOps {
  @log
  sum(a: number, b: number) { return a + b; }
}
new MathOps().sum(2,3);
      
11.13 Using Decorators for Validation
Decorators can enforce validation rules on properties or method parameters. They allow **centralized and reusable validation logic** across classes.
function minLength(len: number) {
  return function(target: any, key: string) {
    let value: string;
    Object.defineProperty(target, key, {
      set(newVal: string) {
        if(newVal.length < len) throw new Error('Too short');
        value = newVal;
      },
      get() { return value; }
    });
  }
}

class User {
  @minLength(3)
  username: string;
}

const u = new User();
u.username = "Ali"; // ok
// u.username = "Al"; // Error
      
11.14 Debugging Decorators
Debugging decorators can be challenging. Use **console.log**, breakpoints, or **Reflect metadata** to inspect decorator execution order, parameters, and returned values. Understanding bottom-up execution helps troubleshoot complex decorator chains.
function debug(target: any, key: string) {
  console.log(`Decorator applied on ${key}`);
}
class Demo {
  @debug
  test() {}
}
      
11.15 Best Practices
Use decorators judiciously to maintain readability. Avoid complex logic inside decorators, favor **reusable decorator factories**, and document their purpose. Ensure **experimentalDecorators** is enabled and leverage **metadata reflection** responsibly. Combine with testing to guarantee predictable behavior.
// Example: Clean reusable decorator
function ReadOnly(target: any, key: string) {
  Object.defineProperty(target, key, { writable: false });
}
class Config {
  @ReadOnly
  version = "1.0";
}
      

12.1 try/catch/finally
TypeScript uses `try/catch/finally` blocks to handle runtime errors gracefully. The `try` block contains code that might throw an error, `catch` handles the error, and `finally` executes code regardless of success or failure. This structure ensures your application can recover from exceptions without crashing and allows logging or cleanup tasks.
try {
  throw new Error('Something went wrong');
} catch (error) {
  console.log('Caught:', error.message);
} finally {
  console.log('Execution completed');
}
      
12.2 Throwing custom errors
Developers can create custom error instances to convey specific failure conditions. By extending the `Error` class, you can define meaningful messages and types, improving debugging and readability.
class CustomError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'CustomError';
  }
}

throw new CustomError('Invalid operation');
      
12.3 Error types
TypeScript recognizes various built-in error types such as `Error`, `TypeError`, `ReferenceError`, and `SyntaxError`. Knowing these types helps categorize and handle errors appropriately.
try {
  null.f(); // TypeError
} catch (e) {
  if (e instanceof TypeError) console.log('Type error occurred');
}
      
12.4 Error inheritance
Custom error classes can extend built-in errors to inherit behavior and enhance type safety. This allows structured exception hierarchies.
class ValidationError extends Error {}
class DatabaseError extends Error {}
      
12.5 Asynchronous error handling
Async code can throw errors in promises or callbacks. Handling them correctly prevents unhandled promise rejections and ensures robust applications.
async function fetchData() {
  try {
    const data = await fetch('invalid_url');
  } catch (err) {
    console.log('Fetch failed:', err);
  }
}
      
12.6 Promise rejection handling
Promises may reject if operations fail. Using `.catch()` or `try/catch` with `async/await` ensures rejection is handled.
fetch('invalid_url')
  .then(res => res.json())
  .catch(err => console.log('Error:', err));
      
12.7 Async/await error handling
Combining `async/await` with `try/catch` provides a readable, synchronous-style error handling approach for asynchronous operations.
async function getData() {
  try {
    const res = await fetch('invalid_url');
    const data = await res.json();
  } catch (err) {
    console.log('Async error:', err);
  }
}
      
12.8 Type-safe error handling
TypeScript allows defining specific error types, enabling better code analysis and reducing runtime mistakes.
try {
  throw new CustomError('Oops');
} catch (err: unknown) {
  if (err instanceof CustomError) {
    console.log(err.message);
  }
}
      
12.9 Logging errors
Logging errors provides traceability and insights into application failures. Proper logging helps debugging and monitoring.
try {
  throw new Error('Log me');
} catch (err) {
  console.error('Error logged:', err);
}
      
12.10 Custom error classes
Creating custom classes extends functionality, adds metadata, and integrates with type systems.
class AuthError extends Error {
  constructor(public code: number, message: string) {
    super(message);
    this.name = 'AuthError';
  }
}
      
12.11 Using enums for error codes
Enums provide structured error codes, improving clarity and maintainability.
enum ErrorCode { NotFound = 404, Unauthorized = 401 }
throw new AuthError(ErrorCode.Unauthorized, 'Access denied');
      
12.12 Validation errors
Validation errors indicate input or data problems, allowing graceful handling and messaging.
function validate(name: string) {
  if(name.length < 3) throw new ValidationError('Name too short');
}
      
12.13 HTTP errors
Handling HTTP errors in API calls ensures user-friendly responses and stability.
fetch('invalid_url')
  .then(res => {
    if(!res.ok) throw new Error('HTTP Error: '+ res.status);
  })
  .catch(err => console.log(err));
      
12.14 Best practices for error handling
Always catch and handle errors, use custom error types, log errors, and separate error handling logic from main code flow.
try {
  riskyOperation();
} catch (err) {
  handleError(err);
}
      
12.15 Debugging techniques
Utilize breakpoints, console logging, stack traces, and TypeScript type checks to identify and resolve errors efficiently.
try {
  riskyOperation();
} catch (err) {
  console.log('Stack trace:', err.stack);
}
      

13.1 Using @types packages:

TypeScript uses @types packages to provide type definitions for popular JavaScript libraries. Installing these packages enables autocomplete, type checking, and safer integration. Most libraries hosted on DefinitelyTyped have corresponding @types packages. By adding npm install @types/library-name, TypeScript can understand the library’s structure and function signatures, preventing runtime errors.

// Install type definitions for Lodash
// npm install lodash @types/lodash

import _ from "lodash";

const numbers: number[] = [1, 2, 3, 4];
const doubled = _.map(numbers, n => n * 2);
console.log(doubled); // Output: [2, 4, 6, 8]
      
13.2 Importing JS libraries:

TypeScript allows importing JavaScript libraries using ES6-style import statements. Even if a library is written in plain JavaScript, you can import it and use type definitions if available. This approach ensures that TypeScript can perform static analysis while integrating third-party libraries.

import axios from "axios";

axios.get("https://api.example.com/data")
  .then(response => console.log(response.data));
      
13.3 Declaring modules for JS libraries:

When using a JavaScript library without TypeScript types, you can declare a module manually using declare module 'library-name'. This tells TypeScript to treat the module as any type, avoiding errors while allowing gradual type integration. Later, you can add proper type definitions for better safety.

declare module "some-js-lib";

import lib from "some-js-lib";

lib.doSomething();
      
13.4 Type assertions for JS functions:

Type assertions in TypeScript allow you to tell the compiler the expected type of a value from a JavaScript function. This is useful when a JS library returns any type or when TypeScript cannot infer the correct type. It ensures type safety without changing the library code.

const element = document.getElementById("app") as HTMLDivElement;
element.innerText = "Hello TypeScript!";
      
13.5 Avoiding any type:

Using any disables type checking, which can lead to runtime errors. TypeScript encourages using precise types or generics instead of any. When integrating JS libraries, prefer type definitions or type assertions over any to maintain safety, readability, and IDE support.

import _ from "lodash";

const values: number[] = [1, 2, 3];
// Avoid: const doubled: any = _.map(values, x => x * 2);
const doubled: number[] = _.map(values, x => x * 2);
      
13.6 Integrating Lodash:

Lodash is a widely used utility library. With TypeScript, you can install @types/lodash to enable type safety. Once integrated, TypeScript provides autocompletion and type checks for all Lodash methods, allowing safer manipulation of arrays, objects, and collections.

import _ from "lodash";

const nums = [1, 2, 3, 4];
const shuffled = _.shuffle(nums);
console.log(shuffled); // Output: [3, 1, 4, 2] (example)
      
13.7 Integrating jQuery:

TypeScript can work with jQuery by installing @types/jquery. This enables type checking for selectors, events, and DOM manipulations. Using TypeScript with jQuery reduces common errors in DOM access and provides IDE support for methods and chaining.

// npm install jquery @types/jquery
import $ from "jquery";

$("#app").text("Hello TypeScript + jQuery!");
      
13.8 Integrating Moment.js:

Moment.js is a date-time library. Using @types/moment, TypeScript understands Moment functions, ensuring correct usage. This helps prevent invalid date operations and enables autocomplete, making date calculations and formatting safer.

import moment from "moment";

const now: string = moment().format("YYYY-MM-DD");
console.log(now); // Output: 2025-08-17 (example)
      
13.9 Integrating Axios:

Axios is a promise-based HTTP client. TypeScript integration ensures typed request and response data. You can define interfaces for API responses, making HTTP calls safer and reducing runtime errors.

import axios from "axios";

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

axios.get("https://api.example.com/user/1")
  .then(response => console.log(response.data.name));
      
13.10 Type-safe third-party APIs:

TypeScript allows defining interfaces or types for third-party API responses. This ensures type safety when consuming APIs. Even if the API is written in JavaScript, defining types in TypeScript allows reliable code and better autocompletion.

interface Post { id: number; title: string; }

async function getPost(): Promise {
  const response = await fetch("https://api.example.com/posts/1");
  return response.json() as Promise;
}
      
13.11 Migrating JS projects to TypeScript:

Migrating a JavaScript project involves gradually renaming files to .ts, adding type annotations, and installing @types packages. TypeScript allows incremental migration by allowing allowJs and checkJs options, making the transition smoother.

// tsconfig.json example
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true
  }
}
      
13.12 Using DefinitelyTyped:

DefinitelyTyped is a repository of high-quality TypeScript type definitions for JavaScript libraries. You can install type definitions via npm using @types/library-name. This provides accurate typing, autocompletion, and safer integration for third-party JS libraries.

// Install types for Chart.js
// npm install chart.js @types/chart.js
import Chart from "chart.js";
      
13.13 Handling untyped modules:

For modules without TypeScript types, declare them using declare module "module-name". This tells TypeScript to treat them as any. Later, you can add proper typings to gradually improve safety while using untyped libraries.

declare module "legacy-lib";

import legacy from "legacy-lib";
legacy.run();
      
13.14 Performance considerations:

Using JavaScript libraries in TypeScript may introduce extra type-checking during compilation. While runtime performance is unaffected, excessive type definitions or unnecessary wrappers can slow compile-time. Keep type definitions minimal and avoid redundant generics for faster builds.

13.15 Best practices:

Always prefer type-safe integrations using @types. Avoid any as much as possible. Use interfaces for API responses, incremental migration for JS projects, and maintain consistent coding standards. Document library usage and keep dependencies updated to prevent type mismatches and runtime errors.

14.1 Callbacks review

Callbacks are one of the earliest techniques used in JavaScript and TypeScript to handle asynchronous operations. A callback is simply a function passed as an argument to another function and executed once an operation is completed. While callbacks are powerful, they often lead to deeply nested code known as “callback hell,” which is hard to read and maintain. In modern TypeScript, callbacks are still useful for simple async tasks, but for complex flows, Promises and async/await are preferred since they provide cleaner and more maintainable code structures.

function fetchData(callback: (data: string) => void) {
  setTimeout(() => {
    callback("Data loaded successfully!");
  }, 1000);
}

fetchData((result) => {
  console.log(result);
});
      
14.2 Promises overview

Promises were introduced to overcome the limitations of callbacks by providing a cleaner way to represent eventual completion or failure of an asynchronous operation. A Promise has three states: pending, fulfilled, or rejected. This allows developers to handle asynchronous results using .then() for success and .catch() for errors. Promises improve code readability by flattening nested callbacks and allowing chaining of operations. In TypeScript, Promises are strongly typed, enabling developers to define the type of resolved value, which helps catch errors during development and provides better tooling support.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise resolved successfully!");
  }, 1000);
});

promise.then((data) => console.log(data)).catch((err) => console.error(err));
      
14.3 Creating promises

Creating Promises in TypeScript is straightforward and useful when wrapping asynchronous logic. A Promise constructor accepts a function with two parameters: resolve and reject. You call resolve when the operation is successful, passing the result, and reject when an error occurs. This design enforces a clear contract between asynchronous producers and consumers. Developers should create promises to wrap asynchronous operations like API calls, timers, or file reads. Promises ensure that the consuming code can handle success and failure in a structured, predictable manner.

function asyncOperation(): Promise {
  return new Promise((resolve, reject) => {
    const success = true;
    if (success) {
      resolve(42);
    } else {
      reject("Operation failed");
    }
  });
}

asyncOperation()
  .then((value) => console.log("Success:", value))
  .catch((error) => console.error("Error:", error));
      
14.4 Chaining promises

One of the major advantages of Promises is the ability to chain them. Chaining allows multiple asynchronous operations to be executed sequentially without nesting callbacks. Each .then() returns a new promise, enabling the next operation to run once the previous one completes. This pattern simplifies workflows like fetching data, processing it, and then storing it. Chaining ensures clean and linear code structure, making asynchronous logic more readable. In TypeScript, strong typing ensures that each step in the chain respects the expected data types, reducing runtime errors.

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => response.json())
  .then((data) => {
    console.log("Post title:", data.title);
    return fetch("https://jsonplaceholder.typicode.com/users/1");
  })
  .then((response) => response.json())
  .then((user) => console.log("User name:", user.name))
  .catch((err) => console.error("Error:", err));
      
14.5 Error handling in promises

Error handling in promises is done using .catch() or by providing a second callback to .then(). If any error occurs in the promise chain, it propagates down to the nearest .catch(). This centralizes error management and prevents code from breaking silently. Developers should always include error handling in promise-based code to ensure resilience against unexpected failures such as network issues or invalid responses. TypeScript enforces typing, so you can clearly define the expected error types, improving debugging and error tracking during development.

new Promise((resolve, reject) => {
  reject("Something went wrong!");
})
  .then((data) => console.log(data))
  .catch((error) => console.error("Caught error:", error));
      
14.6 Async/await syntax

The async/await syntax was introduced as a more readable way to handle promises. Declaring a function with async makes it return a Promise, while the await keyword pauses execution until the Promise resolves or rejects. This makes asynchronous code look synchronous, greatly improving readability. Errors are handled using try/catch blocks. In TypeScript, async/await works seamlessly with type annotations, making it easier to track return values and errors. This approach is now the standard for most asynchronous programming in TypeScript applications.

async function fetchData() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    const data = await response.json();
    console.log("Todo:", data.title);
  } catch (error) {
    console.error("Error:", error);
  }
}

fetchData();
      
14.7 Parallel execution

Parallel execution is useful when multiple asynchronous tasks can run at the same time without depending on each other. Instead of awaiting tasks sequentially, developers can initiate them simultaneously and then wait for their completion. This reduces execution time significantly. In TypeScript, this is commonly achieved with Promise.all(), which waits for all promises to resolve or rejects if any of them fails. Parallel execution is ideal for fetching multiple API endpoints concurrently or processing independent tasks in batch jobs, increasing performance and responsiveness.

async function fetchMultiple() {
  const [post, user] = await Promise.all([
    fetch("https://jsonplaceholder.typicode.com/posts/1").then((res) => res.json()),
    fetch("https://jsonplaceholder.typicode.com/users/1").then((res) => res.json()),
  ]);
  console.log("Post title:", post.title);
  console.log("User name:", user.name);
}

fetchMultiple();
      
14.8 Sequential execution

Sequential execution is necessary when asynchronous operations depend on the results of previous ones. Instead of running everything in parallel, you run each operation one after another using await in a logical order. This ensures that data dependencies are respected. For example, you might need to authenticate a user before fetching their profile, or fetch a list of posts before fetching comments for a specific post. Sequential execution is slower than parallel execution but guarantees correctness when tasks rely on each other’s results.

async function fetchSequential() {
  const post = await fetch("https://jsonplaceholder.typicode.com/posts/1").then((res) => res.json());
  console.log("Post:", post.title);

  const comments = await fetch("https://jsonplaceholder.typicode.com/posts/1/comments").then((res) => res.json());
  console.log("Comments count:", comments.length);
}

fetchSequential();
      
14.9 Promise.all and Promise.race

Promise.all() and Promise.race() are powerful utilities for handling multiple promises. Promise.all() runs promises in parallel and resolves when all succeed, rejecting if any fail. It’s useful when you need multiple results at once. Promise.race(), on the other hand, resolves or rejects as soon as the first promise settles, making it useful for timeouts or picking the fastest result. In TypeScript, both functions preserve type safety, allowing developers to easily infer and handle the return types of multiple asynchronous operations.

async function testPromises() {
  try {
    const results = await Promise.all([
      Promise.resolve("Task 1 done"),
      Promise.resolve("Task 2 done"),
    ]);
    console.log(results);

    const fastest = await Promise.race([
      new Promise((res) => setTimeout(() => res("Fast"), 500)),
      new Promise((res) => setTimeout(() => res("Slow"), 1000)),
    ]);
    console.log("Fastest:", fastest);
  } catch (err) {
    console.error("Error:", err);
  }
}

testPromises();
      
14.10 Cancellation of promises

Unlike some async patterns, native JavaScript Promises cannot be truly canceled once started. However, developers often simulate cancellation by using techniques like AbortController (for fetch requests) or custom flags to ignore results after cancellation. In TypeScript, using AbortController ensures proper typing and prevents memory leaks by halting unnecessary network requests. This is useful for user interfaces where the user may navigate away or change their request, and you want to stop an ongoing operation to save resources and avoid outdated results being processed.

const controller = new AbortController();
const signal = controller.signal;

fetch("https://jsonplaceholder.typicode.com/posts", { signal })
  .then((res) => res.json())
  .then((data) => console.log("Fetched", data.length, "posts"))
  .catch((err) => console.error("Fetch error:", err));

// Cancel the request after 100ms
setTimeout(() => controller.abort(), 100);
      
14.11 Observables (RxJS) overview

Observables, popularized by RxJS, extend asynchronous programming by handling streams of data over time, rather than single values like Promises. They provide operators for transformation, filtering, and combining streams. Observables are especially powerful in applications where multiple events occur continuously, such as user inputs, WebSocket messages, or real-time updates. Unlike Promises, Observables can emit multiple values and be unsubscribed from, giving developers full control over data flow. In TypeScript, RxJS integrates seamlessly with strong typings, improving developer productivity and application maintainability.

import { Observable } from "rxjs";

const observable = new Observable((subscriber) => {
  subscriber.next("First value");
  subscriber.next("Second value");
  setTimeout(() => subscriber.complete(), 1000);
});

observable.subscribe({
  next: (val) => console.log(val),
  complete: () => console.log("Done"),
});
      
14.12 Converting promises to observables

One advantage of RxJS is the ability to convert Promises into Observables, allowing developers to use reactive operators on single asynchronous results. This is often done using the from() function. Once a Promise is converted into an Observable, it can be combined, mapped, or filtered with other streams. This makes integration with existing Promise-based APIs seamless. In TypeScript, converting promises to observables enhances flexibility and gives developers consistent tools for both single-value and multi-value asynchronous workflows within reactive programming paradigms.

import { from } from "rxjs";

const promise = Promise.resolve("Hello from Promise!");
const observable$ = from(promise);

observable$.subscribe((val) => console.log(val));
      
14.13 Async iterators

Async iterators extend the iterator pattern to support asynchronous data sources. By using for await...of, developers can consume asynchronous streams of data in a synchronous-looking loop. Unlike Observables, async iterators are built into modern JavaScript/TypeScript and don’t require additional libraries. They are ideal for processing paginated API responses or streaming data sources. Async iterators improve readability and integrate seamlessly with async/await syntax, providing a natural way to loop through asynchronous sequences. They ensure backpressure handling by awaiting each item before continuing.

async function* asyncGenerator() {
  yield await Promise.resolve("Value 1");
  yield await Promise.resolve("Value 2");
}

(async () => {
  for await (const val of asyncGenerator()) {
    console.log(val);
  }
})();
      
14.14 Performance patterns

Async programming in TypeScript introduces performance considerations. Developers must carefully decide when to run tasks sequentially versus in parallel. Overusing parallel execution may overload servers, while unnecessary sequential execution can slow down applications. Efficient use of batching, caching, debouncing, and throttling can enhance performance. Tools like Promise.allSettled allow handling multiple operations without immediate failure on one. Observables and async iterators also support backpressure handling, which prevents overwhelming consumers. Balancing performance and reliability is key to building scalable TypeScript applications with optimal asynchronous patterns.

async function processBatch(urls: string[]) {
  const results = await Promise.allSettled(
    urls.map((url) => fetch(url).then((res) => res.json()))
  );
  results.forEach((result) => console.log(result.status));
}

processBatch([
  "https://jsonplaceholder.typicode.com/posts/1",
  "https://jsonplaceholder.typicode.com/posts/2",
]);
      
14.15 Best practices

When working with async programming in TypeScript, best practices help maintain clean and reliable code. Always handle errors using try/catch or .catch() to prevent unhandled rejections. Prefer async/await over raw Promises for better readability. Use parallel execution only when tasks are independent, and sequential execution when dependencies exist. For long-running operations, implement cancellation mechanisms with AbortController. Apply strong typing to all async functions to prevent type mismatches. Finally, document and structure async workflows clearly to ensure maintainability and scalability in large applications.

async function getData(url: string): Promise {
  try {
    const response = await fetch(url);
    if (!response.ok) throw new Error("Request failed");
    return await response.json();
  } catch (error) {
    console.error("Error:", error);
    throw error;
  }
}

getData("https://jsonplaceholder.typicode.com/todos/1").then((data) =>
  console.log("Todo:", data.title)
);
      

15.1 Selecting Elements

Selecting DOM elements is the first step to manipulate the webpage. TypeScript supports standard DOM selection methods like getElementById, getElementsByClassName, querySelector, and querySelectorAll. TypeScript’s type system helps ensure that the selected elements are treated correctly, reducing runtime errors when interacting with the DOM.

const heading = document.getElementById("title");
const buttons = document.querySelectorAll(".btn");
      
15.2 Type Assertions with DOM Elements

Type assertions allow developers to tell TypeScript the exact type of a DOM element. This is especially useful when working with form inputs or elements that require specific methods. Using assertions ensures proper property access without compiler errors.

const input = document.getElementById("name") as HTMLInputElement;
console.log(input.value); // safely access input value
      
15.3 Event Listeners

Event listeners allow code to respond to user interactions like clicks, key presses, or mouse movements. TypeScript enhances event handling by providing type safety for event objects, ensuring that event properties and methods are used correctly without runtime surprises.

const button = document.querySelector(".btn")!;
button.addEventListener("click", (event) => {
  console.log("Button clicked!");
});
      
15.4 Mouse and Keyboard Events

Mouse and keyboard events track user interactions on the page. TypeScript provides strong typing for these events, such as MouseEvent and KeyboardEvent, which allows access to relevant properties like coordinates, key codes, or button presses safely.

document.addEventListener("keydown", (event: KeyboardEvent) => {
  console.log(`Key pressed: ${event.key}`);
});
document.addEventListener("click", (event: MouseEvent) => {
  console.log(`Mouse clicked at ${event.clientX}, ${event.clientY}`);
});
      
15.5 Input Validation

Input validation ensures that users provide correct and expected values before processing. TypeScript helps by enforcing types on form input elements and combining runtime checks for completeness, pattern matching, or value ranges. This prevents invalid data from causing runtime errors or inconsistencies.

const ageInput = document.getElementById("age") as HTMLInputElement;
ageInput.addEventListener("input", () => {
  const value = Number(ageInput.value);
  if (value < 0 || value > 120) console.log("Invalid age");
});
      
15.6 Modifying DOM Elements

TypeScript allows developers to safely update element content, attributes, and properties. You can change inner text, inner HTML, or other attributes while keeping type safety, ensuring your DOM updates don’t produce runtime errors or type mismatches.

const title = document.getElementById("title")!;
title.textContent = "Welcome to TypeScript DOM!";
title.setAttribute("data-status", "active");
      
15.7 Creating Elements Dynamically

Dynamic element creation allows adding new elements to the DOM during runtime. TypeScript ensures that the created elements are properly typed, enabling safe property and method access. This is useful for generating lists, cards, or interactive UI components on the fly.

const div = document.createElement("div");
div.textContent = "New dynamic element";
document.body.appendChild(div);
      
15.8 Removing Elements

Removing elements from the DOM can be done safely with TypeScript by selecting and calling remove() or manipulating parent elements. Proper typing ensures that you do not try to remove a null or undefined element, preventing runtime errors.

const oldDiv = document.getElementById("old")!;
oldDiv.remove();
      
15.9 Styling Elements

You can modify CSS styles of elements dynamically using TypeScript. By accessing the style property, you can change colors, sizes, or other CSS properties safely. TypeScript ensures the property exists and helps catch errors early.

const box = document.getElementById("box")!;
box.style.backgroundColor = "lightblue";
box.style.width = "200px";
      
15.10 Working with Forms

TypeScript provides safety when handling forms. By using type assertions on form inputs and elements, you can read values, validate data, and submit forms while avoiding runtime type errors. This is particularly useful for processing text, checkboxes, radio buttons, or selects.

const form = document.querySelector("form")!;
form.addEventListener("submit", (e) => {
  e.preventDefault();
  const input = form.querySelector("#username") as HTMLInputElement;
  console.log(input.value);
});
      
15.11 QuerySelector vs GetElementById

getElementById is specific to IDs and returns a single element, whereas querySelector supports CSS selectors and returns the first matching element. TypeScript’s typing ensures correct use: the former guarantees an element with a specific ID, while the latter may return null for unmatched selectors.

const el1 = document.getElementById("header");
const el2 = document.querySelector(".header"); // first element with class
      
15.12 Event Delegation

Event delegation is a technique where a parent element handles events for its children. This is efficient for dynamically added elements. TypeScript helps by strongly typing event targets, ensuring that you can safely access properties of the child elements that triggered the event.

const list = document.getElementById("list")!;
list.addEventListener("click", (e) => {
  const target = e.target as HTMLLIElement;
  console.log("Clicked item:", target.textContent);
});
      
15.13 Custom Events

Custom events allow you to create your own event types to communicate between components or modules. TypeScript ensures that these events are correctly typed and that their payloads are properly handled, reducing errors in event-driven architectures.

const event = new CustomEvent("myEvent", { detail: { user: "Alice" } });
document.dispatchEvent(event);
document.addEventListener("myEvent", (e) => {
  console.log((e as CustomEvent).detail.user);
});
      
15.14 Integrating with TypeScript Strict Mode

Strict mode enforces stricter type checks, helping prevent null, undefined, or any type errors in DOM manipulation. It ensures that every variable, element, and function is properly typed and that unsafe operations are avoided, increasing code reliability and maintainability.

const input = document.getElementById("email") as HTMLInputElement;
// strict mode ensures input is not null before accessing value
console.log(input.value);
      
15.15 Best Practices

When manipulating the DOM with TypeScript, always use type assertions, handle nullable elements carefully, prefer event delegation for dynamic elements, and separate DOM logic from business logic. Use strict mode, consistent naming, and modular code to make the application maintainable and scalable.

// Example: Safe DOM update
const btn = document.getElementById("save") as HTMLButtonElement;
if (btn) btn.addEventListener("click", () => console.log("Saved!"));
      

16.1 Setting up Node.js with TypeScript

Setting up Node.js with TypeScript involves installing Node.js, initializing a project with npm, and adding TypeScript. You create a tsconfig.json file to configure compilation. This setup allows TypeScript to run with Node.js, providing type safety and modern JavaScript features.

npm init -y
npm install typescript --save-dev
npx tsc --init
      
16.2 Using ts-node

ts-node lets you run TypeScript files directly without compiling them manually. It's ideal for development and testing, simplifying execution and debugging.

npm install -D ts-node
npx ts-node index.ts
      
16.3 File system operations

Node.js's fs module lets you read, write, append, or delete files. Async operations prevent blocking the event loop. TypeScript ensures type safety and avoids runtime errors.

import fs from 'fs';

fs.readFile('example.txt', 'utf-8', (err, data) => {
  if (err) throw err;
  console.log(data);
});
      
16.4 Path module

The path module handles file paths in a cross-platform way, preventing errors when building paths on Windows, macOS, or Linux.

import path from 'path';

const filePath = path.join(__dirname, 'example.txt');
console.log(filePath);
      
16.5 HTTP server creation

Node.js can create a basic HTTP server with the http module. TypeScript ensures type safety for request and response objects, simplifying server development.

import http from 'http';

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello TypeScript with Node.js');
});

server.listen(3000, () => console.log('Server running on port 3000'));
      
16.6 Express.js with TypeScript

Express.js is a lightweight Node.js framework. TypeScript adds type safety to routes, requests, and responses, improving reliability and developer productivity.

import express, { Request, Response } from 'express';
const app = express();

app.get('/', (req: Request, res: Response) => {
  res.send('Hello Express with TypeScript');
});

app.listen(3000, () => console.log('Server running on port 3000'));
      
16.7 Reading environment variables

Environment variables store config settings. Access them with process.env. TypeScript ensures correct types, reducing runtime errors.

process.env.PORT = process.env.PORT || '3000';
const port: number = parseInt(process.env.PORT as string, 10);
console.log('Server port:', port);
      
16.8 Using modules in Node.js

Modules help organize code. TypeScript enforces type safety across modules, supporting ES module or CommonJS syntax.

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

// index.ts
import { add } from './math';
console.log(add(5, 10)); // 15
      
16.9 Async file operations

Node.js supports async file operations using callbacks, promises, or async/await. TypeScript ensures correct type handling.

import fs from 'fs/promises';

async function readFileAsync() {
  const data = await fs.readFile('example.txt', 'utf-8');
  console.log(data);
}

readFileAsync();
      
16.10 Promises in Node.js

Promises simplify asynchronous code by avoiding callback hell. TypeScript provides type safety for resolved values and errors.

function fetchData(): Promise {
  return new Promise((resolve) => {
    setTimeout(() => resolve('Data received'), 1000);
  });
}

fetchData().then(console.log); // Data received
      
16.11 EventEmitter in TypeScript

EventEmitter enables event-driven programming. TypeScript enforces types for event names and payloads, reducing runtime errors.

import { EventEmitter } from 'events';

const emitter = new EventEmitter();
emitter.on('message', (text: string) => console.log(text));
emitter.emit('message', 'Hello EventEmitter');
      
16.12 Debugging Node.js apps

Debug using VS Code debugger, inspector, or console logs. TypeScript catches type errors at compile-time; debugging tools catch logical issues.

node --inspect-brk dist/index.js
      
16.13 Logging in Node.js

Logging tracks app behavior. Use console or libraries like Winston or Pino. TypeScript ensures consistent logging interfaces.

import { createLogger, transports, format } from 'winston';

const logger = createLogger({
  level: 'info',
  format: format.simple(),
  transports: [new transports.Console()]
});

logger.info('Server started');
      
16.14 Testing Node.js apps

Testing ensures correctness. Jest or Mocha allow unit and integration tests. TypeScript adds type safety to test code.

import { add } from './math';

test('adds numbers correctly', () => {
  expect(add(2, 3)).toBe(5);
});
      
16.15 Best practices

Follow best practices: modular code, async/await, error handling, environment configs, logging, testing, and TypeScript for type safety. This improves maintainability and scalability.

async function getData(file: string): Promise {
  try {
    return await fs.readFile(file, 'utf-8');
  } catch (err) {
    console.error(err);
    throw err;
  }
}
      

17.1 Introduction to Testing
Testing ensures that your code behaves as expected and helps catch bugs early. In TypeScript, testing also validates type correctness along with functionality. Proper testing reduces regression issues, improves maintainability, and boosts developer confidence. It involves writing test cases for functions, classes, or components and checking their outputs against expected results. Automated testing frameworks make this process easier and more reliable.
// Simple test concept example
function add(a: number, b: number): number {
    return a + b;
}
console.log(add(2, 3)); // Expected: 5
      
17.2 Jest with TypeScript
Jest is a popular testing framework that works seamlessly with TypeScript. It provides features like mocks, assertions, and snapshot testing. With the help of ts-jest, TypeScript files can be tested directly. Jest supports parallel test execution, fast feedback, and detailed reporting, making it ideal for modern TypeScript projects.
// Jest test example
import { add } from './math';
test('adds two numbers', () => {
    expect(add(2, 3)).toBe(5);
});
      
17.3 Mocha & Chai
Mocha is a flexible test runner, while Chai is an assertion library. Together, they allow writing expressive and readable tests. TypeScript integration is possible using type definitions. Mocha provides hooks for setup and teardown, making it suitable for unit, integration, and functional testing.
// Mocha & Chai example
import { expect } from 'chai';
import { add } from './math';
describe('add function', () => {
    it('should add numbers correctly', () => {
        expect(add(2,3)).to.equal(5);
    });
});
      
17.4 Unit Tests
Unit tests focus on testing the smallest units of code, such as functions or classes, in isolation. They help ensure that individual components behave correctly. In TypeScript, unit tests also validate type safety. Writing comprehensive unit tests reduces bugs and allows safe refactoring.
// Unit test example
function multiply(a: number, b: number) { return a*b; }
console.log(multiply(2, 3)); // 6
      
17.5 Integration Tests
Integration tests verify that multiple modules or components work together correctly. Unlike unit tests, they test interactions and data flow across boundaries. In TypeScript projects, integration testing ensures that typed contracts and APIs function as expected in combination.
// Integration test concept
function fetchData(id: number) {
    return { id, name: "Test" };
}
function processData(data: { id: number, name: string }) {
    return data.name.toUpperCase();
}
console.log(processData(fetchData(1))); // "TEST"
      
17.6 Mocking Modules
Mocking allows replacing real modules or functions with controlled fake versions during tests. This isolates units and prevents external dependencies from affecting test results. In TypeScript, libraries like Jest support module mocking, ensuring type safety while simulating behavior.
// Jest mocking example
jest.mock('./api', () => ({
    fetchData: () => ({ id: 1, name: 'Mocked' })
}));
      
17.7 Async Testing
Asynchronous operations like API calls or timers require async testing to ensure correctness. TypeScript’s async/await works seamlessly with testing frameworks like Jest or Mocha. Proper async tests prevent false positives and ensure promises resolve correctly.
// Async test example
async function fetchData() { return "data"; }
fetchData().then(result => console.log(result)); // "data"
      
17.8 Snapshot Testing
Snapshot testing captures the output of components or functions and compares it to a stored snapshot. It is commonly used in UI testing with frameworks like Jest. Changes in output trigger test failures, helping detect unintended modifications. In TypeScript, snapshots work alongside strong typing.
// Jest snapshot example
import renderer from 'react-test-renderer';
const tree = renderer.create().toJSON();
expect(tree).toMatchSnapshot();
      
17.9 Debugging in VS Code
VS Code provides a powerful debugging environment for TypeScript. You can set breakpoints, inspect variables, evaluate expressions, and step through code. Integrated debugging ensures that both logic and type-related issues can be identified and resolved efficiently during development.
// Debugging: set a breakpoint on the following line
const x = 10;
console.log(x); // Inspect variable 'x' here
      
17.10 Using Breakpoints
Breakpoints pause code execution at specific lines. In VS Code, you can click beside the line number to set a breakpoint, run the debugger, and inspect values. Conditional breakpoints allow pausing only when a condition is met, aiding complex bug investigations.
// Example: set breakpoint on the next line
for(let i=0;i<5;i++){
    console.log(i); // Execution stops here when debugging
}
      
17.11 Logging Best Practices
Logging helps track code execution and identify bugs. Use meaningful messages, consistent formats, and avoid excessive logging in production. Libraries like Winston or Pino provide structured logging for TypeScript projects. Logging combined with breakpoints improves debugging efficiency.
console.log("User data fetched:", { id: 1, name: "Alice" });
      
17.12 Error Stack Traces
Stack traces show the sequence of function calls leading to an error. They are invaluable for pinpointing the source of runtime issues. In TypeScript, compiled JavaScript stack traces may differ, so source maps are used to map errors back to original TypeScript code.
try {
    throw new Error("Something went wrong");
} catch(e) {
    console.error(e.stack);
}
      
17.13 Type Errors Debugging
Type errors occur when a value does not match its declared type. TypeScript provides compile-time checks, reducing runtime bugs. Tools like VS Code highlight type mismatches immediately, making debugging fast and accurate. Resolving type errors improves code reliability and maintainability.
let num: number = 5;
// num = "string"; // Error: Type 'string' is not assignable to type 'number'
      
17.14 Linters & Formatters
Linters like ESLint and formatters like Prettier enforce consistent code style, detect errors, and prevent common mistakes. They integrate with TypeScript, highlighting issues in IDEs or during build processes. Using them ensures cleaner, maintainable, and bug-free code.
// ESLint identifies potential issues, Prettier formats code automatically
const foo = "bar"; // auto-formatted and lint-checked
      
17.15 Test Coverage Tools
Test coverage tools measure how much of your code is executed during testing. Tools like Jest's built-in coverage reports or Istanbul help identify untested code. High coverage ensures reliable software, detects missing tests, and improves confidence in code correctness.
// Jest coverage command
// jest --coverage
      

18.1 Setting up React with TypeScript
To use TypeScript in React, create a project with `create-react-app` using the TypeScript template. This ensures the project is configured to compile `.ts` and `.tsx` files with proper type checking.
npx create-react-app my-app --template typescript
cd my-app
npm start
      
18.2 Functional Components
Functional components are simpler and can be typed using `FC` (FunctionComponent) from React, which allows specifying props types.
import React, { FC } from 'react';

interface Props {
  name: string;
}

const Greeting: FC<Props> = ({ name }) => {
  return <h1>Hello, {name}</h1>;
};
      
18.3 Props Typing
Typing props ensures components receive the correct data and helps with auto-completion in IDEs.
interface ButtonProps {
  label: string;
  onClick: () => void;
}

const Button: FC<ButtonProps> = ({ label, onClick }) => <button onClick={onClick}>{label}</button>;
      
18.4 State Typing
When using `useState`, you can specify the state type explicitly for safety.
const [count, setCount] = useState<number>(0);
      
18.5 Event Handling
Typing events ensures the correct properties are available for DOM events.
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  console.log(e.currentTarget);
};
      
18.6 Using useRef and useState
`useRef` can be typed to hold specific element references, improving type safety.
const inputRef = useRef<HTMLInputElement | null>(null);
const [value, setValue] = useState<string>('');
      
18.7 Context API with TypeScript
Context API allows sharing state across components with defined types.
interface ThemeContextProps {
  darkMode: boolean;
  toggle: () => void;
}

const ThemeContext = React.createContext<ThemeContextProps | undefined>(undefined);
      
18.8 React Hooks Typing
Custom hooks can be typed for input parameters and return types.
function useCounter(initial: number) : [number, () => void] {
  const [count, setCount] = useState<number>(initial);
  const increment = () => setCount(count + 1);
  return [count, increment];
}
      
18.9 Higher-Order Components Typing
HOCs can be typed using generics to pass props correctly.
function withLogger<P>(Component: React.ComponentType<P>) {
  return (props: P) => {
    console.log('Props:', props);
    return <Component {...props} />;
  };
}
      
18.10 Component Composition
Composing components is enhanced by typing props to avoid runtime errors.
const Card: FC<{title: string}> = ({ title, children }) => <div><h2>{title}</h2>{children}</div>;
      
18.11 React Router Typing
Typing route props ensures navigation and params are correctly handled.
import { useParams } from 'react-router-dom';

interface Params { id: string; }
const { id } = useParams<Params>();
      
18.12 Form Handling
Forms can be typed using event types and state.
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  console.log('Submitted');
};
      
18.13 Third-Party Component Typing
Many libraries have TypeScript types. Always install `@types` packages.
import Select from 'react-select';
// @types/react-select for typings
      
18.14 React Performance Patterns
Typing helps optimize rendering and memoization.
const MemoComp = React.memo<FC<{value: number}>>(({ value }) => <div>{value}</div>);
      
18.15 Best Practices in React TS
Use explicit typing for props, state, and hooks. Leverage interfaces for clarity and maintainability. Install type definitions for all libraries and use React.FC where appropriate.
interface Props { message: string; }
const Alert: FC<Props> = ({ message }) => <div>{message}</div>;
      

19.1 Angular Overview
Angular is a **TypeScript-based framework** for building dynamic web applications. It provides a **component-based architecture**, two-way data binding, dependency injection, and tools for building scalable applications. Angular leverages TypeScript's type system for safer code, making large-scale app development more maintainable and error-resistant.
// Angular component example
@Component({
  selector: 'app-root',
  template: '<h1>Hello Angular</h1>'
})
export class AppComponent {}
      
19.2 TypeScript in Angular CLI
Angular CLI generates TypeScript projects with proper configurations. TypeScript ensures type safety and better IDE support while compiling down to JavaScript for browser compatibility.
// Generate Angular project
ng new my-app --strict
cd my-app
ng serve
      
19.3 Components in Angular
Components are the building blocks of Angular applications. Each component has a **TypeScript class, HTML template, and optional CSS styles**. They manage the view and logic.
@Component({
  selector: 'app-user',
  template: '<div>User component</div>'
})
export class UserComponent {
  name: string = 'Alice';
}
      
19.4 Modules
Angular modules (`NgModule`) group components, directives, and services into cohesive units, making app organization simpler and enabling lazy loading.
@NgModule({
  declarations: [AppComponent, UserComponent],
  imports: [BrowserModule],
  bootstrap: [AppComponent]
})
export class AppModule {}
      
19.5 Services and Dependency Injection
Services hold reusable business logic. Angular injects services into components using the **dependency injection system**, promoting separation of concerns.
@Injectable({ providedIn: 'root' })
export class UserService {
  getUsers() { return ['Alice','Bob']; }
}

@Component({ selector: 'app-user-list', template: '<div></div>' })
export class UserListComponent {
  constructor(private userService: UserService) {}
}
      
19.6 Directives
Directives are classes that modify DOM behavior. `ngIf`, `ngFor` are structural, while attribute directives change appearance or behavior of elements.
<div *ngIf="isVisible">Visible content</div>
<div *ngFor="let user of users">{{user}}</div>
      
19.7 Pipes
Pipes transform data in templates, e.g., formatting dates or uppercase strings.
<p>{{ today | date:'fullDate' }}</p>
<p>{{ name | uppercase }}</p>
      
19.8 Forms in Angular
Angular supports **template-driven** and **reactive forms**. TypeScript provides type safety for form controls.
// Reactive form example
this.form = new FormGroup({
  username: new FormControl('', Validators.required)
});
      
19.9 HTTP Client
Angular's `HttpClient` provides type-safe HTTP requests with observable responses.
this.http.get<User[]>('/api/users').subscribe(data => console.log(data));
      
19.10 RxJS in Angular
RxJS allows reactive programming with observables. Angular uses observables for async operations like HTTP requests or event streams.
this.users$ = this.http.get<User[]>('/api/users');
this.users$.subscribe(u => console.log(u));
      
19.11 Angular Lifecycle Hooks
Hooks like `ngOnInit`, `ngOnDestroy` allow executing code during component lifecycle events.
ngOnInit() { console.log('Component initialized'); }
ngOnDestroy() { console.log('Cleanup'); }
      
19.12 Routing and Navigation
Angular Router provides navigation between components, with route parameters and guards.
const routes: Routes = [
  { path: 'users', component: UserListComponent }
];
      
19.13 Guards and Interceptors
Guards control route access; interceptors handle HTTP requests/responses for logging, authentication, or error handling.
@Injectable()
export class AuthGuard implements CanActivate {
  canActivate() { return true; }
}
      
19.14 Testing Angular TS Code
Angular supports unit and integration testing with Jasmine/Karma or Jest. Components and services can be tested with type safety.
describe('UserService', () => {
  it('should return users', () => {
    expect(service.getUsers()).toContain('Alice');
  });
});
      
19.15 Best Practices
Use TypeScript types consistently, leverage Angular CLI, modularize components and services, use reactive forms and observables, and write unit tests for maintainable, scalable Angular applications.
// Follow consistent coding standards and type safety
      

20.1 Vue Overview
Vue.js is a **progressive JavaScript framework** for building user interfaces. When combined with TypeScript, Vue gains **type safety, autocompletion, and improved maintainability**. It supports both Options API and Composition API, making it versatile for different project sizes.
// Vue 3 with TS setup example
import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');
      
20.2 Vue CLI Setup with TypeScript
Using Vue CLI, you can create a Vue project with TypeScript support, configuring linting and class-style components.
# Terminal command
vue create my-vue-app
# Select TypeScript support
      
20.3 Components in Vue
Components are **reusable UI elements** in Vue. TypeScript allows typing props, events, and data to ensure safer component interactions.
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  name: 'HelloWorld'
});
</script>
      
20.4 Props Typing
Props are **input properties** passed to components. TypeScript ensures props are of correct type.
export default defineComponent({
  props: {
    msg: String
  }
});
      
20.5 Reactive State
Vue's `reactive` function allows **reactive state objects**, and TypeScript ensures type safety on the state properties.
import { reactive } from 'vue';
const state = reactive({ count: 0 });
state.count++;
      
20.6 Computed Properties
Computed properties **derive values from reactive state** and are type-checked with TypeScript.
import { computed } from 'vue';
const doubleCount = computed(() => state.count * 2);
      
20.7 Watchers
Watchers allow reacting to state changes. TypeScript ensures the watched property type is correct.
import { watch } from 'vue';
watch(() => state.count, (newVal, oldVal) => {
  console.log(`Count changed: ${oldVal} => ${newVal}`);
});
      
20.8 Vue Router with TypeScript
Vue Router supports TypeScript, enabling **typed route definitions** and navigation.
import { createRouter, createWebHistory } from 'vue-router';
const routes = [{ path: '/', component: Home }];
const router = createRouter({ history: createWebHistory(), routes });
      
20.9 Vuex State Management
Vuex provides **centralized state management**, and TypeScript ensures type-safe mutations, actions, and getters.
import { createStore } from 'vuex';
const store = createStore({ state: { count: 0 }, mutations: { increment(state) { state.count++ } } });
      
20.10 Composition API
Composition API allows organizing logic in **reusable functions**, with TypeScript adding static typing.
import { ref } from 'vue';
const count = ref<number>(0);
      
20.11 Options API Typing
The Options API can also be typed using `defineComponent` and `props` definitions.
export default defineComponent({
  data() { return { count: 0 }; },
  methods: { increment() { this.count++ } }
});
      
20.12 Event Handling
TypeScript ensures **typed event payloads** in component communication.
<button @click="increment">Increment</button>
      
20.13 Slots and Templates
Slots allow **component content injection**, with TypeScript typing for props passed through slots.
<slot name="header" />
      
20.14 Integrating Third-Party Libraries
TypeScript helps with **typing external libraries** and ensuring proper usage.
import _ from 'lodash';
const arr: number[] = [1,2,3];
console.log(_.reverse(arr));
      
20.15 Best Practices
Use **Composition API for logic reuse**, TypeScript for type safety, keep components small, and prefer typed props and state for maintainability.
// Keep components modular and type-safe
      

21.1 Functional Programming in TS
Functional programming in TypeScript emphasizes **pure functions, immutability, and higher-order functions**. It reduces side effects, improves readability, and enhances testability. By leveraging TypeScript’s type system, developers can catch errors at compile-time while writing functional code that manipulates data declaratively.
// Example: Higher-order function
const mapArray = (arr: number[], fn: (n: number) => number) => arr.map(fn);
console.log(mapArray([1,2,3], n => n*2));
      
21.2 Currying and Partial Application
Currying transforms a function with multiple arguments into a **sequence of unary functions**. Partial application pre-fills some arguments, producing new functions. Both improve code reusability and functional composition.
// Currying example
const multiply = (a: number) => (b: number) => a * b;
const double = multiply(2);
console.log(double(5)); // 10
      
21.3 Immutability Patterns
Immutability ensures objects or arrays are **not modified directly**, preventing side effects. Techniques include `readonly` properties, `Object.freeze`, and returning new objects from functions. TypeScript enforces immutability at compile time for safer code.
interface Person { readonly name: string; age: number; }
const user: Person = { name: "Ali", age: 25 };
// user.name = "Sara"; // Error
const newUser = { ...user, age: 26 };
      
21.4 Composition over Inheritance
Instead of deep inheritance hierarchies, **composition** favors combining small, reusable behaviors. This approach improves flexibility and reduces tight coupling, making code easier to maintain.
const canEat = (state: any) => ({
  eat: () => console.log(`${state.name} is eating`)
});

const canWalk = (state: any) => ({
  walk: () => console.log(`${state.name} is walking`)
});

const person = { name: "Ali", ...canEat({name:"Ali"}), ...canWalk({name:"Ali"}) };
person.eat();
person.walk();
      
21.5 Mixins in TypeScript
Mixins are a way to **compose classes** with shared behavior without inheritance. They allow multiple features to be combined into a single class while preserving type safety.
// Example: mixins
type Constructor = new (...args: any[]) => T;

function Flyer(Base: TBase) {
  return class extends Base { fly() { console.log("Flying"); } };
}

class Animal { eat() { console.log("Eating"); } }
const Bird = Flyer(Animal);
const b = new Bird();
b.eat();
b.fly();
      
21.6 Decorator Patterns
Decorator patterns **add behavior to classes or methods dynamically**. TypeScript supports experimental decorators for logging, caching, or validation, enabling modular enhancements.
function Log(target: any, key: string, desc: PropertyDescriptor) {
  const original = desc.value;
  desc.value = function(...args: any[]) {
    console.log(`Calling ${key} with`, args);
    return original.apply(this, args);
  };
}

class Calculator {
  @Log
  add(a: number, b: number) { return a+b; }
}
new Calculator().add(5,10);
      
21.7 Singleton Pattern
The singleton pattern ensures a class has **only one instance**. It is commonly used for logging, configuration, or shared resources. TypeScript supports singletons using private constructors and static methods.
class Singleton {
  private static instance: Singleton;
  private constructor() {}
  static getInstance() {
    if(!Singleton.instance) Singleton.instance = new Singleton();
    return Singleton.instance;
  }
}
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true
      
21.8 Factory Pattern
Factory patterns **create objects without exposing the instantiation logic**. They provide a common interface while allowing flexible creation of different types.
interface Shape { draw(): void; }
class Circle implements Shape { draw() { console.log("Circle"); } }
class Square implements Shape { draw() { console.log("Square"); } }

class ShapeFactory {
  static create(type: string): Shape {
    if(type==="circle") return new Circle();
    return new Square();
  }
}

const shape = ShapeFactory.create("circle");
shape.draw();
      
21.9 Observer Pattern
Observer pattern **allows objects to subscribe to events** from another object. It decouples the sender and receiver, enabling reactive programming and event-driven architecture.
class Subject {
  private observers: Function[] = [];
  subscribe(fn: Function) { this.observers.push(fn); }
  notify(data: any) { this.observers.forEach(fn => fn(data)); }
}
const subject = new Subject();
subject.subscribe((msg: string) => console.log("Observer:", msg));
subject.notify("Hello");
      
21.10 Strategy Pattern
Strategy pattern defines **a family of algorithms** and makes them interchangeable. It allows selecting an algorithm at runtime without modifying the context.
class Context {
  constructor(private strategy: (x: number, y: number) => number) {}
  execute(a: number, b: number) { return this.strategy(a,b); }
}

const add = (a:number,b:number) => a+b;
const multiply = (a:number,b:number) => a*b;

const context = new Context(add);
console.log(context.execute(2,3)); // 5
      
21.11 Mediator Pattern
Mediator pattern centralizes **communication between objects**, reducing dependencies. Components communicate through a mediator rather than directly, simplifying complex relationships.
class Mediator {
  notify(sender: string, event: string) { console.log(`${sender} triggers ${event}`); }
}
class ComponentA { constructor(private mediator: Mediator) {} trigger() { this.mediator.notify("A","event"); } }
const m = new Mediator();
const a = new ComponentA(m);
a.trigger();
      
21.12 Adapter Pattern
Adapter pattern **translates one interface into another**, allowing incompatible interfaces to work together. It’s useful when integrating third-party libraries.
interface OldAPI { oldRequest(): void; }
class NewAPI { request() { console.log("New Request"); } }

class Adapter implements OldAPI {
  constructor(private newApi: NewAPI) {}
  oldRequest() { this.newApi.request(); }
}

const api = new Adapter(new NewAPI());
api.oldRequest();
      
21.13 Proxy Pattern
Proxy pattern **controls access to objects**. It can be used for logging, caching, validation, or security, providing a surrogate or placeholder for the real object.
class RealSubject {
  request() { console.log("Real request"); }
}
class ProxySubject {
  private real = new RealSubject();
  request() { console.log("Proxy checks"); this.real.request(); }
}
const p = new ProxySubject();
p.request();
      
21.14 Builder Pattern
Builder pattern separates the construction of complex objects from their representation. It allows step-by-step creation of objects with different configurations while keeping code readable.
class Car {
  wheels!: number;
  color!: string;
}
class CarBuilder {
  private car = new Car();
  setWheels(n: number) { this.car.wheels = n; return this; }
  setColor(c: string) { this.car.color = c; return this; }
  build() { return this.car; }
}
const car = new CarBuilder().setWheels(4).setColor("Red").build();
console.log(car);
      
21.15 Real-World Pattern Examples
Real-world TypeScript applications leverage patterns like **singleton for DB connections, factory for service creation, observer for events, strategy for algorithms, and builder for complex objects**. Using patterns improves maintainability, scalability, and code readability.
// Example: Singleton DB connection
class Database {
  private static instance: Database;
  private constructor() {}
  static getInstance() {
    if(!Database.instance) Database.instance = new Database();
    return Database.instance;
  }
}
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1===db2); // true
      

22.1 How tsc works
The TypeScript compiler (tsc) **parses TypeScript code, checks types, and emits JavaScript** compatible with the target environment. Understanding tsc helps developers debug compilation issues and optimize code.
$ tsc app.ts
      
22.2 AST (Abstract Syntax Tree) overview
AST represents TypeScript code as a **tree structure** where nodes correspond to syntax elements. The compiler uses AST for **type checking, code transformation, and optimization**.
function add(a: number, b: number) { return a + b; }
// AST: FunctionDeclaration -> Parameters -> ReturnStatement
      
22.3 Type checking process
Type checking ensures that **values match declared types**. The compiler traverses AST nodes and validates types against declarations.
let num: number = 5;
// num = 'hello'; // Error: Type 'string' is not assignable to type 'number'
      
22.4 Transpilation vs compilation
Transpilation converts TypeScript to JavaScript **without generating low-level machine code**, while compilation may produce executable code.
$ tsc app.ts --target ES6
      
22.5 Incremental compilation
Incremental compilation speeds up large projects by **reusing previously compiled outputs**.
/* tsconfig.json */
{
  "compilerOptions": {
    "incremental": true
  }
}
      
22.6 Type declaration files
Declaration files (.d.ts) provide **type information for JS libraries**.
$ npm install --save-dev @types/lodash
      
22.7 Module resolution
Module resolution determines how **imported files are located**.
import _ from 'lodash';
import { add } from './utils';
      
22.8 Source maps generation
Source maps link **compiled JS back to TypeScript source** for debugging.
$ tsc app.ts --sourceMap
      
22.9 Compiler diagnostics
Diagnostics provide **detailed information about errors and warnings**.
$ tsc app.ts --pretty
      
22.10 Watch mode internals
Watch mode recompiles automatically on **file changes**.
$ tsc -w
      
22.11 tsconfig parsing
tsconfig.json **configures the compiler** with paths, targets, and options.
/* tsconfig.json */
{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs"
  }
}
      
22.12 Compiler APIs
TypeScript exposes APIs to **programmatically parse, type check, and emit JS**.
import * as ts from 'typescript';
const program = ts.createProgram(['app.ts'], {});
      
22.13 Language service
Language service powers **intellisense, autocomplete, and error detection** in editors.
// Used in VSCode or IDEs to provide type info dynamically
      
22.14 Performance optimization
Optimize compilation by **incremental builds, excluding unnecessary files, and caching**.
/* tsconfig.json */
{
  "exclude": ["node_modules"]
}
      
22.15 Debugging compilation issues
Use **diagnostics, verbose logging, and tsconfig tweaks** to identify issues.
$ tsc --traceResolution
      

23.1 Partial type:

The Partial utility type in TypeScript constructs a type with all properties of the given type set to optional. This is useful when updating objects partially or initializing objects incrementally. It ensures type safety while allowing flexibility to omit some fields.

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

const updateUser = (user: Partial) => {
  console.log(user);
};

updateUser({ name: "Majid" }); // Only 'name' provided
      
23.2 Required type:

The Required utility type converts all optional properties of a type to required. This is useful when you want to enforce full object initialization after initially allowing partial definitions. TypeScript ensures all fields are present at compile-time.

interface User { name?: string; age?: number; }

const fullUser: Required = { name: "Majid", age: 30 };
// Error if any property missing
      
23.3 Readonly type:

The Readonly type makes all properties of a type immutable. Once defined, properties cannot be reassigned. This is useful for constants or preventing accidental modifications of object data while maintaining type safety.

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

const user: Readonly = { name: "Majid", age: 30 };
// user.age = 31; // Error: Cannot assign to 'age'
      
23.4 Record type:

The Record type constructs an object type with specified keys and value types. It is useful to map keys to values uniformly. TypeScript ensures that all defined keys exist and have the correct value type, preventing errors.

type Grades = "math" | "science";
const studentGrades: Record = {
  math: 90,
  science: 85
};
      
23.5 Pick type:

The Pick type allows selecting a subset of properties from an existing type. It is useful when you need only specific fields for a function or a view model, maintaining type safety without redefining the type.

interface User { name: string; age: number; email: string; }

type UserPreview = Pick;
const preview: UserPreview = { name: "Majid", email: "majid@example.com" };
      
23.6 Omit type:

The Omit type constructs a type by excluding specified properties from an existing type. It helps in scenarios where you want most of the type's fields but want to exclude sensitive or irrelevant ones.

type UserWithoutEmail = Omit;
const user2: UserWithoutEmail = { name: "Majid", age: 30 };
      
23.7 Exclude type:

The Exclude type removes types from a union. It is useful to filter out specific types while keeping type safety. For example, you can exclude null or undefined values from a union.

type T = string | number | null;
type NonNull = Exclude; // string | number
      
23.8 Extract type:

The Extract type extracts types from a union that are assignable to another type. It is the opposite of Exclude and is useful for filtering a union down to the desired subset while preserving type safety.

type T2 = string | number | boolean;
type NumOrBool = Extract; // number | boolean
      
23.9 NonNullable type:

The NonNullable type removes null and undefined from a type. It is useful when you want to enforce that a value must exist and cannot be null or undefined, reducing runtime errors.

type Name = string | null | undefined;
type SafeName = NonNullable; // string only
      
23.10 ReturnType utility:

The ReturnType utility type extracts the return type of a function. This allows reusing return types for variables or other functions without repeating the type declaration, keeping code DRY.

function getUser() { return { name: "Majid", age: 30 }; }
type UserType = ReturnType; 
const user3: UserType = { name: "Majid", age: 30 };
      
23.11 Parameters utility:

The Parameters utility type extracts the parameter types of a function as a tuple. This is useful to reuse argument types or forward parameters safely between functions.

function greet(name: string, age: number) {}
type GreetParams = Parameters; // [string, number]
      
23.12 InstanceType utility:

The InstanceType utility extracts the instance type of a class constructor. This allows defining variables or parameters that must match the type of an object created by the class.

class UserClass { name = "Majid"; age = 30; }
type UserInstance = InstanceType;
const userObj: UserInstance = new UserClass();
      
23.13 Conditional types:

Conditional types in TypeScript allow defining types that depend on other types. Using T extends U ? X : Y syntax, you can create type logic similar to if-else conditions at compile time, enabling flexible and reusable types.

type IsString = T extends string ? "Yes" : "No";
type Test1 = IsString; // Yes
type Test2 = IsString; // No
      
23.14 Mapped types:

Mapped types allow transforming properties of a type into a new type. By iterating over keys, you can create modified types like Readonly, Partial, or custom transformations. This is powerful for scalable, generic type manipulations.

type UserKeys = "name" | "age";
type UserReadOnly = { readonly [K in UserKeys]: string | number };
const user4: UserReadOnly = { name: "Majid", age: 30 };
// user4.age = 31; // Error
      
23.15 Advanced real-world examples:

Combining advanced types and utility types helps create robust and type-safe applications. For example, APIs can return partially defined objects using Partial, while Mapped and Conditional types can enforce transformations or filter data dynamically. Using ReturnType, Parameters, and InstanceType ensures consistency across functions and class instances, preventing bugs and improving maintainability in large TypeScript projects.

// Example: Using Partial, Pick, and Conditional Types
interface Post { id: number; title: string; content: string; }

type UpdatePost = Partial>;
const updatedPost: UpdatePost = { title: "New Title" }; // Valid update
      

24.1 DOM API typing
TypeScript provides **strong typing for DOM APIs**, ensuring safer manipulation of HTML elements and their properties.
const btn = document.getElementById('myBtn') as HTMLButtonElement;
btn.disabled = true;
      
24.2 Fetch API with TS
Fetch API can be typed to **ensure the response data structure is known**, reducing runtime errors.
fetch('https://api.example.com/data')
  .then((res) => res.json() as Promise<DataType>)
  .then(data => console.log(data));
      
24.3 WebSockets typing
WebSocket messages can be typed for **strong type checks on data received and sent**.
const ws = new WebSocket('ws://localhost');
ws.onmessage = (event: MessageEvent<string>) => console.log(event.data);
      
24.4 LocalStorage & SessionStorage
TypeScript can enforce **string-based storage operations** with type assertions.
localStorage.setItem('key', JSON.stringify({name: 'Ali'}));
const data = JSON.parse(localStorage.getItem('key')!) as {name: string};
      
24.5 IndexedDB
TypeScript types help manage **IndexedDB database structure and transaction data**.
const request = indexedDB.open('MyDB', 1);
request.onsuccess = (event) => {
  const db = (event.target as IDBOpenDBRequest).result;
};
      
24.6 Canvas API typing
Canvas context methods are typed to **ensure correct usage and property access**.
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d');
ctx!.fillStyle = 'red';
ctx!.fillRect(0,0,100,100);
      
24.7 Geolocation API
TypeScript types provide **structured access to location coordinates** and callbacks.
navigator.geolocation.getCurrentPosition((position: GeolocationPosition) => {
  console.log(position.coords.latitude, position.coords.longitude);
});
      
24.8 Web Workers typing
TypeScript allows **typed messages and events** in worker threads.
const worker = new Worker('worker.js');
worker.onmessage = (e: MessageEvent<string>) => console.log(e.data);
worker.postMessage('Hello Worker');
      
24.9 EventSource API
TypeScript types help **handle server-sent events safely**.
const evtSource = new EventSource('/events');
evtSource.onmessage = (e: MessageEvent<string>) => console.log(e.data);
      
24.10 Service Workers
TypeScript types allow **proper registration and messaging** with service workers.
navigator.serviceWorker.register('/sw.js')
  .then(reg => console.log('Service Worker registered'));
      
24.11 Notifications API
Typed notification options help ensure **correct API usage**.
new Notification('Hello', {body: 'You have a message'});
      
24.12 Media APIs (audio/video)
TypeScript typing allows **safe access to media streams and elements**.
const video = document.querySelector('video') as HTMLVideoElement;
navigator.mediaDevices.getUserMedia({video: true}).then(stream => video.srcObject = stream);
      
24.13 Drag & drop
TypeScript ensures **typed drag events and element references**.
const el = document.getElementById('drag') as HTMLElement;
el.ondragstart = (e: DragEvent) => console.log(e.dataTransfer);
      
24.14 File API
Typed file inputs help handle **files safely in TS**.
const input = document.querySelector('input[type=file]') as HTMLInputElement;
input.onchange = (e: Event) => {
  const file = (e.target as HTMLInputElement).files![0];
  console.log(file.name);
};
      
24.15 Best practices
Always type **Web API interactions**, handle errors gracefully, and use **feature detection** to ensure **cross-browser compatibility**.
// Example best practice
if('geolocation' in navigator) {
  navigator.geolocation.getCurrentPosition(pos => console.log(pos.coords));
}
      

25.1 Parsing JSON
JSON.parse converts a JSON string into a JavaScript object. TypeScript typing helps **ensure the parsed object conforms to expected types**.
const jsonString = '{"name": "Ali", "age": 25}';
const obj: { name: string; age: number } = JSON.parse(jsonString);
      
25.2 JSON typing
Typing JSON responses ensures **type safety when accessing properties**.
interface User { name: string; age: number; }
const user: User = JSON.parse('{"name": "Ali", "age": 25}');
      
25.3 Serializing objects
JSON.stringify converts objects into JSON strings, preserving type information for communication or storage.
const user = { name: 'Ali', age: 25 };
const jsonStr: string = JSON.stringify(user);
      
25.4 Type-safe API responses
Using interfaces or types for API responses ensures **correct data handling**.
interface ApiResponse { id: number; name: string; }
const res = await fetch('/api/user');
const data: ApiResponse = await res.json();
      
25.5 Interfaces for JSON
Defining interfaces allows **structured access to JSON data**.
interface Todo { id: number; title: string; completed: boolean; }
const todo: Todo = JSON.parse('{"id":1,"title":"Task","completed":false}');
      
25.6 Nested JSON typing
Typing nested JSON objects ensures **safe access to inner properties**.
interface Post { id: number; author: { name: string; id: number } }
const post: Post = JSON.parse('{"id":1,"author":{"name":"Ali","id":10}}');
      
25.7 Optional fields
Optional properties in interfaces handle **JSON fields that may be missing**.
interface User { name: string; age?: number; }
const u1: User = JSON.parse('{"name":"Ali"}');
const u2: User = JSON.parse('{"name":"Ali","age":25}');
      
25.8 Default values
Assign default values when JSON fields are absent.
const userData = JSON.parse('{"name":"Ali"}');
const age: number = userData.age ?? 18;
      
25.9 JSON schema validation
Validate JSON structure using schemas to **catch errors at runtime**.
// Using AJV library
import Ajv from 'ajv';
const schema = { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] };
const ajv = new Ajv();
const validate = ajv.compile(schema);
const valid = validate(JSON.parse('{"name":"Ali"}'));
      
25.10 Generics for JSON
Generics allow creating **type-safe functions for different JSON structures**.
function parseJSON<T>(json: string): T { return JSON.parse(json); }
const user = parseJSON<{name:string;age:number}>('{"name":"Ali","age":25}');
      
25.11 Runtime validation
Runtime checks ensure **JSON matches expected type structures**.
const obj: any = JSON.parse('{"name":"Ali"}');
if(typeof obj.name !== 'string') throw new Error('Invalid data');
      
25.12 Using io-ts library
io-ts provides **runtime type checking and decoding**.
import * as t from 'io-ts';
const User = t.type({ name: t.string, age: t.number });
const result = User.decode(JSON.parse('{"name":"Ali","age":25}'));
      
25.13 Zod library overview
Zod allows **declarative runtime validation and parsing** for JSON.
import { z } from 'zod';
const User = z.object({ name: z.string(), age: z.number() });
const parsed = User.parse(JSON.parse('{"name":"Ali","age":25}'));
      
25.14 Error handling in JSON
Catch parsing or validation errors to **avoid runtime crashes**.
try {
  const user = JSON.parse('{"name":"Ali"}');
} catch(e) {
  console.error('Invalid JSON', e);
}
      
25.15 Best practices
Always type JSON responses, use interfaces for structured data, validate inputs, and handle errors gracefully to ensure **type safety and robust applications**.
interface User { name: string; age: number; }
const user: User = JSON.parse('{"name":"Ali","age":25}');
      

26.1 Efficient compilation
Efficient compilation ensures TypeScript projects build faster and reduce wait times. Using incremental builds and watch mode helps speed up the compilation process.
$ tsc --incremental
      
26.2 Tree shaking
Tree shaking removes unused code from the final bundle, improving performance.
import { usedFunction } from './utils';
// Unused functions are removed during bundling
      
26.3 Avoiding unnecessary any types
Using 'any' excessively reduces type safety and can impact optimizations. Prefer explicit types.
let name: string = 'Ali'; // instead of let name: any
      
26.4 Module bundlers (Webpack, Rollup)
Bundlers optimize code, manage dependencies, and perform tree shaking and minification.
// webpack.config.js example
export default {
  entry: './src/index.ts',
  output: { filename: 'bundle.js' },
};
      
26.5 Lazy loading
Lazy loading splits code so that modules are loaded only when needed, reducing initial load time.
const module = await import('./featureModule');
      
26.6 Code splitting
Code splitting creates separate bundles for different parts of the application, improving performance.
// webpack example
optimization: { splitChunks: { chunks: 'all' } }
      
26.7 Minification & compression
Minification removes unnecessary whitespace and compresses code to reduce file size.
// Using Terser plugin in Webpack
const TerserPlugin = require('terser-webpack-plugin');
      
26.8 Memory optimization
Optimize memory by avoiding large in-memory structures and using efficient data types.
let arr: number[] = new Array(1000); // allocate efficiently
      
26.9 Using const & readonly
'const' prevents reassignment and 'readonly' prevents object property changes, improving reliability and clarity.
const PI = 3.14;
class Circle {
  readonly radius: number;
  constructor(r: number) { this.radius = r; }
}
      
26.10 Avoiding type assertions
Excessive use of type assertions can bypass compiler checks. Prefer proper typing.
let val: unknown = 'test';
let str: string = val as string; // use only when necessary
      
26.11 Profiling TypeScript apps
Use tools to measure performance and identify bottlenecks.
// Node.js profiling example
node --inspect-brk app.js
      
26.12 Compiler options for speed
Certain tsconfig options like 'incremental', 'skipLibCheck', and 'noEmitOnError' can improve compilation speed.
/* tsconfig.json */
{
  "compilerOptions": {
    "incremental": true,
    "skipLibCheck": true
  }
}
      
26.13 Incremental builds
Incremental builds compile only changed files, reducing build time for large projects.
$ tsc --incremental
      
26.14 Reducing type complexity
Simplify types and avoid deeply nested generics to improve compilation speed.
type Simple = string | number;
      
26.15 Best practices
Combine incremental builds, lazy loading, tree shaking, and proper typing to optimize TypeScript performance.
// Follow clean coding and optimized typing principles
      

27.1 Introduction to GraphQL
GraphQL is a query language for APIs that allows clients to request exactly the data they need. Using TypeScript with GraphQL enables **type-safe API queries and responses**, reducing runtime errors and improving developer experience.
// Example GraphQL query
const query = `
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
    }
  }
`;
      
27.2 Setting up Apollo Client
Apollo Client is a popular library to interact with GraphQL APIs. TypeScript ensures **typed queries, mutations, and cache** usage.
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client: ApolloClient<any> = new ApolloClient({
  uri: '/graphql',
  cache: new InMemoryCache(),
});
      
27.3 Typed queries
Defining **TypeScript interfaces for GraphQL queries** ensures the data returned matches expected types.
interface User { id: string; name: string; }
const { data } = await client.query<{ user: User }, { id: string } >({
  query: GET_USER,
  variables: { id: '1' }
});
      
27.4 Typed mutations
Mutations can be typed similarly to queries to ensure **safe updates to data**.
interface UpdateUserResponse { updateUser: User; }
const { data } = await client.mutate<UpdateUserResponse, { id: string; name: string }>({
  mutation: UPDATE_USER,
  variables: { id: '1', name: 'Ali' }
});
      
27.5 GraphQL schema typing
Using TypeScript with GraphQL schemas ensures **compile-time validation** of queries and server responses.
// Example type generated from schema
interface Query {
  user(id: string): User;
}
      
27.6 Scalars & enums
GraphQL scalars and enums can be mapped to TypeScript types for **type safety**.
type Role = 'ADMIN' | 'USER';
interface User { role: Role; }
      
27.7 Input types
Input objects in GraphQL are mapped to TypeScript interfaces to **type-check mutation inputs**.
interface UpdateUserInput {
  id: string;
  name?: string;
}
      
27.8 Interfaces & unions
TypeScript interfaces and union types can model **GraphQL interface and union types**.
interface Admin { role: 'ADMIN'; permissions: string[]; }
interface User { role: 'USER'; }
type Account = Admin | User;
      
27.9 Fragments in TypeScript
GraphQL fragments can be typed for **reuse across queries and type safety**.
const userFragment = `
  fragment UserFields on User {
    id
    name
  }
`;
      
27.10 Code generation (GraphQL Code Generator)
GraphQL Code Generator creates **TypeScript types automatically** from your schema and queries, reducing manual typing errors.
// Install and run codegen
$ graphql-codegen --config codegen.yml
      
27.11 Query variables typing
Variables passed to queries can be typed to **ensure correct structure and types**.
interface GetUserVars { id: string; }
      
27.12 Response typing
Query responses should be typed to **match expected GraphQL data**.
interface GetUserResponse { user: User; }
      
27.13 Error handling
Type-safe error handling ensures that **GraphQL errors are caught and typed**.
try {
  const { data } = await client.query<GetUserResponse, GetUserVars>({ query: GET_USER, variables: { id: '1' } });
} catch (error) {
  console.error(error);
}
      
27.14 Subscriptions typing
GraphQL subscriptions can be typed to **ensure messages match expected structures**.
const subscription = client.subscribe<{ message: string }>({ query: MESSAGE_SUBSCRIPTION });
subscription.subscribe({
  next({ data }) { console.log(data.message); }
});
      
27.15 Best practices
Always generate **TypeScript types from GraphQL schema**, use typed queries/mutations, and enforce **strong typing for variables and responses**.
// Example: use generated types for safe queries
const { data } = await client.query<GetUserQuery, GetUserQueryVariables>({ query: GET_USER, variables: { id: '1' } });
      

28.1 Type safety and security
TypeScript ensures that **data types are correctly used**, preventing many type-related security issues like buffer overflow or injection attacks.
let username: string = 'admin';
// username = 123; // Error: Type 'number' is not assignable to type 'string'
      
28.2 Input validation
Always validate user inputs to prevent **malicious data injection**.
function isValidName(name: string): boolean {
  return /^[a-zA-Z]+$/.test(name);
}
      
28.3 XSS prevention
Escape or sanitize inputs before rendering to prevent **cross-site scripting attacks**.
function escapeHTML(str: string) {
  return str.replace(/&/g, '&')
            .replace(//g, '>');
}
      
28.4 CSRF protection
Use **tokens or same-site cookies** to prevent cross-site request forgery.
// Example: Adding CSRF token to request header
fetch('/api/data', { headers: { 'X-CSRF-Token': token } });
      
28.5 Avoiding eval and unsafe code
Never use **eval** or dynamically executed strings to prevent code injection.
// Avoid
// eval('alert(1)');
      
28.6 Safe JSON parsing
Always handle errors when parsing JSON to prevent **malformed data execution**.
try {
  const obj = JSON.parse(userInput);
} catch (err) {
  console.error('Invalid JSON');
}
      
28.7 Strict null checks
Enable **strictNullChecks** to avoid null or undefined errors.
let name: string | null = null;
// name.toLowerCase(); // Error if strictNullChecks is true
      
28.8 HTTPS enforcement
Always serve apps over **HTTPS** to secure communication.
// Example: redirect HTTP to HTTPS in server config
      
28.9 Secure cookies
Use **HttpOnly** and **Secure** flags to protect cookies.
document.cookie = 'session=abc; Secure; HttpOnly';
      
28.10 Secure localStorage usage
Never store **sensitive data** directly in localStorage.
// Example: encrypt data before storing
localStorage.setItem('token', btoa(token));
      
28.11 Dependency audit
Regularly audit **npm dependencies** for vulnerabilities.
$ npm audit
      
28.12 Secrets management
Keep secrets outside codebase using **environment variables**.
const apiKey = process.env.API_KEY;
      
28.13 Avoiding prototype pollution
Do not modify object prototypes from external input to prevent **prototype pollution**.
// Safe approach
const obj = Object.create(null);
obj.user = 'Ali';
      
28.14 Webpack security tips
Use **output hashing, module validation, and tree-shaking** to minimize attack surface.
// webpack.config.js example
module.exports = { mode: 'production', devtool: 'source-map' };
      
28.15 Best security practices
Follow **type safety, strict checks, input validation, HTTPS, secure storage, dependency audit**, and avoid unsafe constructs for robust security.
// Example: Combined security practices
function safeParse(str: string) {
  try {
    return JSON.parse(str);
  } catch { return null; }
}
      

29.1 Introduction to i18n
Internationalization (i18n) enables applications to support **multiple languages and regions**. TypeScript provides **type safety** for translation messages, helping developers catch errors at compile time and ensuring consistency across multilingual applications.
// Example: Message interface
interface Messages {
  greeting: string;
}
const en: Messages = { greeting: 'Hello' };
      
29.2 Using i18next with TS
i18next is a **widely used localization library**. TypeScript typings ensure proper handling of translation keys and language configurations.
import i18next from 'i18next';
i18next.init({
  lng: 'en',
  resources: { en: { translation: { greeting: 'Hello' } } }
});
      
29.3 Message typing
Type-safe messages prevent **missing translations** and mismatches between translation keys and values.
interface Translation { greeting: string; }
const messages: Translation = { greeting: 'Hello' };
      
29.4 Language switching
Dynamically switching languages updates content without breaking type safety.
i18next.changeLanguage('fr');
      
29.5 Pluralization
Handle plural forms properly using i18next's pluralization support and TypeScript interfaces.
i18next.t('item', { count: 2 }); // returns '2 items'
      
29.6 Date & time localization
Localize dates and times using `Intl.DateTimeFormat` with type safety.
const date = new Date();
const formatted = new Intl.DateTimeFormat('fr-FR').format(date);
      
29.7 Number formatting
Format numbers according to locale with TypeScript support.
const num = 123456.78;
const formatted = new Intl.NumberFormat('de-DE').format(num);
      
29.8 Currency formatting
Format monetary values using locale-sensitive formatting.
const amount = 1000;
const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
      
29.9 Translation loading
Load translations asynchronously to support dynamic content.
i18next.loadLanguages(['fr', 'de']);
      
29.10 Context-based translations
Use context to differentiate translation based on usage.
i18next.t('button.save', { context: 'admin' });
      
29.11 Dynamic messages
Interpolate values into translation strings safely.
i18next.t('greeting_user', { name: 'Ali' });
      
29.12 Integration with React
Use `react-i18next` for type-safe localization in React components.
import { useTranslation } from 'react-i18next';
const { t } = useTranslation();
<p>{t('greeting')}</p>
      
29.13 Integration with Angular
Angular components can use i18next with TypeScript interfaces for safe translation.
import { TranslateService } from '@ngx-translate/core';
constructor(private translate: TranslateService) {}
this.translate.get('greeting').subscribe(msg => console.log(msg));
      
29.14 Error handling in i18n
Handle missing keys or loading errors gracefully.
i18next.on('missingKey', (lng, ns, key) => console.warn(`Missing ${key} in ${lng}`));
      
29.15 Best practices
Maintain consistent keys, use TypeScript interfaces for messages, and validate translations. Always test across locales to ensure correctness.
// Use interfaces and type-safe loading
interface Messages { greeting: string; }
      

30.1 Unit testing fundamentals
Unit testing involves testing **individual units of code** (like functions or classes) to ensure correctness. Tools like Jest or Mocha are used to isolate and validate behavior.
// Example: Jest unit test
function sum(a: number, b: number) { return a + b; }

test('sum adds numbers', () => {
  expect(sum(2, 3)).toBe(5);
});
      
30.2 Integration testing
Integration testing ensures that **multiple units work together correctly**, e.g., services interacting with a database.
// Example: Integration test with API call
import request from 'supertest';
import app from './app';

test('GET /users returns 200', async () => {
  const res = await request(app).get('/users');
  expect(res.status).toBe(200);
});
      
30.3 Mocking in Jest
Mocking replaces real dependencies with **fake implementations** to isolate tests.
// Example: Mocking a module
jest.mock('./api');
import { fetchData } from './api';

fetchData.mockResolvedValue({ data: 'mock' });
      
30.4 Spy functions
Spy functions monitor calls to functions **without modifying behavior**. Useful for verifying function usage.
const spy = jest.spyOn(console, 'log');
console.log('Hello');
expect(spy).toHaveBeenCalledWith('Hello');
      
30.5 Asynchronous testing
Test async functions using **async/await or promises**, ensuring proper behavior after async completion.
test('async test', async () => {
  const result = await fetchData();
  expect(result.data).toBeDefined();
});
      
30.6 Snapshot testing
Snapshot testing captures **component output or function results** and compares with stored snapshots for changes.
test('component snapshot', () => {
  const tree = renderer.create(<MyComponent />).toJSON();
  expect(tree).toMatchSnapshot();
});
      
30.7 Testing React components
React component tests check **rendering and behavior**. React Testing Library simplifies testing UI interactions.
import { render, screen } from '@testing-library/react';
import App from './App';

render(<App />);
expect(screen.getByText('Hello')).toBeInTheDocument();
      
30.8 Testing Angular components
Angular components are tested with **TestBed**, verifying rendering, services, and interactions.
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';

TestBed.configureTestingModule({ declarations: [AppComponent] });
const fixture = TestBed.createComponent(AppComponent);
expect(fixture.componentInstance).toBeTruthy();
      
30.9 End-to-end testing
E2E testing validates the **whole application flow**, ensuring UI, backend, and APIs work together.
// Example: Cypress test
cy.visit('/');
cy.contains('Login').click();
cy.url().should('include', '/dashboard');
      
30.10 Cypress overview
Cypress is a framework for **E2E testing**, providing fast, reliable browser-based automation.
// Visit page and interact
element = cy.get('#submit');
element.click();
      
30.11 Test coverage reporting
Coverage tools measure **how much code is executed during tests**, helping identify untested paths.
// Jest coverage
jest --coverage
      
30.12 Continuous testing
Integrate tests in CI/CD pipelines to **automate and maintain code quality**.
// GitHub Actions example
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm install
      - run: npm test
      
30.13 Test-driven development (TDD)
TDD emphasizes **writing tests before code**, ensuring implementation meets requirements.
test('adds numbers', () => {
  expect(sum(2, 3)).toBe(5);
});
// Then implement sum function
      
30.14 Behavior-driven development (BDD)
BDD focuses on **behavior specification** using descriptive language like `describe` and `it`.
describe('Calculator', () => {
  it('adds numbers correctly', () => {
    expect(sum(1,2)).toBe(3);
  });
});
      
30.15 Best testing practices
Follow principles like **small isolated tests, clear naming, mocking dependencies, and code coverage** to maintain reliable tests.
// Example: simple Jest test structure
describe('MyModule', () => {
  test('function behaves correctly', () => {
    expect(func()).toBeTruthy();
  });
});
      

31.1 State management overview
State management in TypeScript applications ensures **consistent, predictable, and maintainable state**. It helps manage data flow across components and services, making applications scalable and easier to debug.
// Example: Simple state object
interface AppState {
  count: number;
}
const state: AppState = { count: 0 };
      
31.2 Redux with TypeScript
Redux is a predictable state container. Using TypeScript with Redux **enhances type safety** for actions, reducers, and state.
import { createStore } from 'redux';
interface State { count: number; }
const initialState: State = { count: 0 };
      
31.3 Actions and action types
Actions describe **state changes**. TypeScript allows defining **strongly-typed action types**.
interface IncrementAction { type: 'INCREMENT'; }
interface DecrementAction { type: 'DECREMENT'; }
type Action = IncrementAction | DecrementAction;
      
31.4 Reducers typing
Reducers specify how state changes based on actions. Typing ensures **correct state and action handling**.
function counterReducer(state: State, action: Action): State {
  switch(action.type) {
    case 'INCREMENT': return { count: state.count + 1 };
    case 'DECREMENT': return { count: state.count - 1 };
    default: return state;
  }
}
      
31.5 Store typing
Typing the store guarantees **correct state access and dispatching**.
const store = createStore(counterReducer, initialState);
store.dispatch({ type: 'INCREMENT' });
      
31.6 Thunks
Thunks allow **asynchronous actions** in Redux, typed with TypeScript for safety.
import { ThunkAction } from 'redux-thunk';
const incrementAsync = (): ThunkAction<void, State, unknown, Action> => dispatch => {
  setTimeout(() => dispatch({ type: 'INCREMENT' }), 1000);
};
      
31.7 Middleware typing
Middleware intercepts actions. TypeScript **ensures proper typing** for dispatch and state.
const logger = store => next => action => {
  console.log('Dispatching', action);
  return next(action);
};
      
31.8 React Context API
Context API manages state **without prop drilling**. TypeScript ensures **typed context values**.
import React, { createContext, useContext } from 'react';
interface AppContext { count: number; }
const AppContext = createContext<AppContext>({ count: 0 });
      
31.9 MobX typing
MobX uses **observable state**. TypeScript ensures **observable properties and actions are typed**.
import { observable } from 'mobx';
class Store {
  @observable count = 0;
}
      
31.10 Zustand typing
Zustand provides **simpler store management** with typed state and actions.
import create from 'zustand';
const useStore = create<{ count: number; increment: () => void }>(() => ({
  count: 0,
  increment: () => {} 
}));
      
31.11 NgRx overview
NgRx is a **Redux-inspired state library for Angular**, typed with TypeScript for actions, selectors, and reducers.
import { createReducer, on } from '@ngrx/store';
      
31.12 Vuex with TypeScript
Vuex manages **state in Vue apps**. TypeScript ensures **typed state, getters, and mutations**.
import { createStore } from 'vuex';
interface State { count: number; }
const store = createStore<State>({ state: { count: 0 } });
      
31.13 State immutability
Immutability ensures **previous state is not modified**, enabling reliable debugging and predictable updates.
const newState = { ...state, count: state.count + 1 };
      
31.14 Async state handling
Handling asynchronous state with **promises or async/await** ensures proper flow of updates.
async function fetchAndUpdate() {
  const data = await fetch('/api').then(res => res.json());
}
      
31.15 Best practices
Use **typed state, avoid mutations, leverage libraries wisely**, and ensure **scalable and maintainable state management**.
// Example: Always type actions and state
interface State { count: number; }
interface Action { type: string; payload?: any; }
      

32.1 Introduction to Webpack
Webpack is a module bundler that bundles JavaScript, TypeScript, and assets into optimized output files. It handles dependencies, transformations, and build optimization.
// webpack.config.js
module.exports = { entry: './src/index.ts', output: { filename: 'bundle.js' } };
      
32.2 ts-loader setup
ts-loader integrates TypeScript with Webpack, allowing the compiler to process .ts files.
// webpack.config.js
module: { rules: [ { test: /\.ts$/, use: 'ts-loader' } ] }
      
32.3 Babel integration
Babel can be used with Webpack for transpiling modern JS/TS syntax for compatibility.
// webpack.config.js
module: { rules: [ { test: /\.ts$/, use: ['babel-loader', 'ts-loader'] } ] }
      
32.4 Entry and output configuration
Entry defines the starting file; output specifies the bundled file location.
entry: './src/index.ts',
output: { path: __dirname + '/dist', filename: 'bundle.js' }
      
32.5 Loaders and plugins
Loaders transform files; plugins extend Webpack functionality like optimization or HTML generation.
module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] },
plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ]
      
32.6 Source maps
Source maps link compiled JS to original TS code for easier debugging.
devtool: 'source-map'
      
32.7 Hot module replacement
HMR allows updating modules without full page reload.
plugins: [ new webpack.HotModuleReplacementPlugin() ]
devServer: { hot: true }
      
32.8 Code splitting
Splits code into smaller chunks for optimized loading.
output: { filename: '[name].bundle.js' },
optimization: { splitChunks: { chunks: 'all' } }
      
32.9 Tree shaking
Removes unused code during bundling to reduce file size.
mode: 'production'
      
32.10 Production vs development builds
Production builds are optimized; development builds include debugging tools.
mode: 'development' // or 'production'
      
32.11 Environment variables
Define env variables using Webpack plugins.
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') })
      
32.12 Performance optimization
Use caching, minification, and bundle analysis to improve performance.
optimization: { minimize: true }
      
32.13 Module resolution
Configure Webpack to resolve file extensions and alias paths.
resolve: { extensions: ['.ts', '.js'], alias: { '@': path.resolve(__dirname, 'src') } }
      
32.14 Multiple entry points
Define multiple entries for multi-page applications.
entry: { main: './src/index.ts', admin: './src/admin.ts' }
      
32.15 Best bundling practices
Use loaders/plugins wisely, optimize for production, use source maps for debugging, and split code for performance.
// Always analyze bundle size
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [ new BundleAnalyzerPlugin() ]
      

33.1 Docker basics
Docker allows **containerization** of applications, providing isolated, reproducible environments.
# Check Docker version
$ docker --version
      
33.2 Dockerfile for TypeScript apps
Use Dockerfile to define **environment, dependencies, and build steps**.
# Dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]
      
33.3 Multi-stage builds
Reduces image size by separating **build and runtime stages**.
# Dockerfile multi-stage
FROM node:18 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:18
WORKDIR /app
COPY --from=build /app/dist ./dist
CMD ["node", "dist/index.js"]
      
33.4 Node.js container setup
Ensure Node.js container has **correct working directory and ports exposed**.
EXPOSE 3000
WORKDIR /app
COPY . .
CMD ["node", "dist/index.js"]
      
33.5 Mounting volumes
Use volumes to **persist data** or mount code for live reload.
docker run -v $(pwd):/app -p 3000:3000 my-app
      
33.6 Docker Compose
Docker Compose allows **multi-container orchestration**.
# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
      - ".:/app"
    command: npm start
      
33.7 Environment variables
Pass config via environment variables for **flexibility and security**.
ENV NODE_ENV=production
# or docker-compose environment section
      
33.8 Container networking
Connect multiple containers using **bridge networks**.
docker network create my-network
docker run --network my-network ...
      
33.9 Health checks
Ensure container is **running properly** using health check instructions.
HEALTHCHECK CMD curl --fail http://localhost:3000 || exit 1
      
33.10 Logging
Access container logs for **debugging and monitoring**.
docker logs -f my-container
      
33.11 Building images
Build the Docker image with **tagging**.
docker build -t my-app:latest .
      
33.12 Pushing to Docker Hub
Push images for **distribution and deployment**.
docker push myusername/my-app:latest
      
33.13 Kubernetes overview
Kubernetes orchestrates **deployment, scaling, and management** of containers.
kubectl get pods
kubectl apply -f deployment.yaml
      
33.14 Deployment pipelines
Automate building, testing, and deploying TypeScript apps using **CI/CD pipelines**.
# Example GitHub Actions
name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm install
      - run: npm run build
      - run: docker build -t my-app .
      
33.15 Best practices
Use multi-stage builds, environment variables, health checks, logging, and automated deployment pipelines for **robust TypeScript apps**.
# Keep images small and secure
# Separate build and runtime
# Use CI/CD for deployment
      

34.1 REST API basics
REST APIs allow **communication over HTTP** using standard methods like GET, POST, PUT, DELETE, enabling clients to interact with servers.
// Example GET request
fetch('/api/users').then(res => res.json()).then(data => console.log(data));
      
34.2 Creating API with Express.js
Express.js with TypeScript enables **type-safe server creation** and routing.
import express from 'express';
const app = express();
app.get('/users', (req, res) => { res.json([{name: 'Ali'}]); });
app.listen(3000);
      
34.3 Routing typing
Type routes with **Request and Response types** for safety.
import { Request, Response } from 'express';
app.get('/users', (req: Request, res: Response) => { res.send('OK'); });
      
34.4 Request and response types
Define request and response payloads using **interfaces**.
interface User { name: string; age: number; }
app.post('/users', (req: Request<{}, {}, User>, res: Response) => { res.send(req.body); });
      
34.5 Middleware typing
Type middlewares for **req, res, next** to ensure type safety.
import { NextFunction } from 'express';
app.use((req: Request, res: Response, next: NextFunction) => { console.log(req.url); next(); });
      
34.6 Error handling
TypeScript allows typed **error handling** in Express.
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.message);
  res.status(500).send(err.message);
});
      
34.7 Query parameters typing
Access query parameters safely with interfaces.
interface Query { search: string; }
app.get('/search', (req: Request<{}, {}, {}, Query>, res: Response) => {
  res.send(req.query.search);
});
      
34.8 Path parameters typing
Type route params for safety.
interface Params { id: string; }
app.get('/users/:id', (req: Request<Params>, res: Response) => {
  res.send(req.params.id);
});
      
34.9 Body parsing typing
Type request body to **ensure expected structure**.
interface UserBody { name: string; }
app.use(express.json());
app.post('/users', (req: Request<{}, {}, UserBody>, res: Response) => { res.send(req.body.name); });
      
34.10 Async API handling
Use **async/await** for handling asynchronous endpoints.
app.get('/users', async (req: Request, res: Response) => {
  const users = await getUsers();
  res.send(users);
});
      
34.11 Response validation
Validate response data before sending.
const users: User[] = await getUsers();
res.json(users);
      
34.12 API documentation typing
Use **typed interfaces** for API docs like Swagger.
/**
 * @swagger
 * /users:
 *   get:
 *     responses:
 *       200:
 *         description: List of users
 */
      
34.13 Using OpenAPI
OpenAPI helps **type-safe API documentation**.
import swaggerJsdoc from 'swagger-jsdoc';
const specs = swaggerJsdoc({ definition, apis: ['./routes/*.ts'] });
      
34.14 Authentication & authorization
Type authentication middleware for security.
interface User { id: string; name: string; }
app.use((req: Request, res: Response, next: NextFunction) => { /* auth logic */ next(); });
      
34.15 Best practices
Always define interfaces, type request and response objects, handle errors, validate inputs, and use async/await consistently.
// Example: Typed Express route
interface Params { id: string; }
app.get('/users/:id', (req: Request<Params>, res: Response) => { res.send(req.params.id); });
      

35.1 Introduction to microservices
Microservices architecture divides an application into **small, independent services** communicating over APIs. TypeScript enhances microservices by providing **type safety** and better maintainability across services.
// Microservice folder structure example
services/
  user-service/
  order-service/
      
35.2 Service typing
Define types for service data and requests to **ensure consistent contracts** between services.
interface User { id: string; name: string; }
interface CreateUserRequest { name: string; }
      
35.3 Inter-service communication
Services communicate via **HTTP, gRPC, or message brokers**. TypeScript ensures **type safety** in requests and responses.
// Example HTTP call between services
fetch('http://order-service/orders').then(res => res.json());
      
35.4 gRPC with TypeScript
gRPC provides **strongly-typed RPC communication**. TypeScript interfaces can match gRPC-generated types.
// Generated gRPC TypeScript code
import { UserServiceClient } from './proto/user_grpc_pb';
      
35.5 REST microservices
TypeScript is used to **type REST APIs** ensuring requests and responses are validated.
app.get('/users', (req: Request, res: Response) => {
  res.json(users);
});
      
35.6 Message brokers
Use TypeScript to **type messages** sent via Kafka, RabbitMQ, or NATS for event-driven communication.
interface UserCreatedEvent { id: string; name: string; }
      
35.7 Event-driven architecture
Microservices react to events. TypeScript ensures **event payloads** are correctly typed.
// Event listener example
on('user.created', (event: UserCreatedEvent) => console.log(event.name));
      
35.8 Data consistency
TypeScript helps enforce **schemas and contracts** for distributed data consistency.
// Using types for consistency
interface Order { id: string; userId: string; total: number; }
      
35.9 Logging & monitoring
Use TypeScript-typed logging libraries to track service behavior and monitor performance.
import { logger } from './logger';
logger.info('User service started');
      
35.10 Circuit breaker pattern
Implement circuit breakers in TypeScript to **prevent cascading failures**.
// Pseudocode
if(failureCount > 5) openCircuit();
      
35.11 API gateways
API gateways manage requests to microservices. TypeScript ensures **typed request routing**.
// Example: Gateway forwarding request
forwardRequest<User>('/users');
      
35.12 Service discovery
Services locate each other dynamically. TypeScript helps maintain **correct types** when connecting.
const userServiceUrl: string = discoverService('user-service');
      
35.13 Testing microservices
Use TypeScript to **type test inputs and expected outputs** for unit and integration testing.
test('create user', () => { expect(createUser('Ali').name).toBe('Ali'); });
      
35.14 Scaling microservices
TypeScript ensures **type-safe scaling**, allowing adding new instances or services without breaking contracts.
// Horizontal scaling configuration example
replicas: 3
      
35.15 Best practices
Use TypeScript interfaces for all service contracts, validate messages, ensure type safety in API calls, and leverage automated tests.
interface PaymentServiceRequest { amount: number; userId: string; }
      

36.1 Introduction to FP
Functional Programming (FP) emphasizes **pure functions, immutability, and declarative code**. TypeScript provides **type safety** for FP patterns.
// Pure function example
const add = (a: number, b: number): number => a + b;
      
36.2 Pure functions
Pure functions **do not produce side effects** and always return the same output for the same input.
const multiply = (x: number, y: number): number => x * y;
      
36.3 Immutability
Data should not be mutated; instead, **new objects/arrays** are returned.
const arr = [1,2,3];
const newArr = [...arr, 4]; // original arr unchanged
      
36.4 Higher-order functions
Functions that **accept or return other functions**.
const map = (f: (x: number) => number) => (arr: number[]) => arr.map(f);
      
36.5 Currying
Transforming a function with multiple arguments into a sequence of functions each taking one argument.
const addC = (a: number) => (b: number) => a + b;
const sum5 = addC(5);
sum5(3); // 8
      
36.6 Partial application
Pre-filling some arguments of a function.
const multiplyC = (a: number, b: number) => a * b;
const double = multiplyC.bind(null, 2);
double(5); // 10
      
36.7 Function composition
Combining multiple functions into one.
const compose = (f: Function, g: Function) => (x: any) => f(g(x));
      
36.8 Monads overview
Monads encapsulate **computation with context**, e.g., Option, Either for handling values safely.
// Using Maybe from fp-ts
import { some, none } from 'fp-ts/Option';
      
36.9 Option/Maybe types
Represent **presence or absence of a value** safely.
const value = some(5);
const noValue = none;
      
36.10 Either type
Represents **success (Right) or failure (Left)**.
import { left, right } from 'fp-ts/Either';
const result = right(42);
      
36.11 Functor concept
Functors **map over wrapped values**.
value.map(x => x * 2); // Option functor mapping
      
36.12 FP libraries (Ramda, fp-ts)
Libraries provide **functional utilities and types** for FP in TypeScript.
import * as R from 'ramda';
R.map(x => x * 2, [1,2,3]); // [2,4,6]
      
36.13 Declarative coding patterns
Write **what to do** rather than **how to do it** using FP constructs.
const evens = [1,2,3,4].filter(x => x % 2 === 0);
      
36.14 Error handling with FP
Use **Option/Either types** instead of exceptions for safer code.
import { tryCatch } from 'fp-ts/Either';
const result = tryCatch(() => JSON.parse('{"a":1}'), e => e);
      
36.15 Best practices
Prefer **pure functions, immutability, and type-safe FP utilities**. Use FP libraries for common patterns.
const increment = (x: number) => x + 1;
      

37.1 Building CLI apps
Building CLI (Command-Line Interface) applications in TypeScript allows developers to create tools that run directly in the terminal. TypeScript provides **type safety, autocomplete, and error detection**, making CLI development robust and maintainable. CLI apps can automate tasks, parse input arguments, and interact with the file system, enhancing productivity. Using TypeScript ensures fewer runtime errors and easier refactoring for large CLI projects.
<!-- Example: Simple CLI app -->
console.log('Welcome to My CLI App!');
      
37.2 Using yargs
**Yargs** is a library for parsing command-line arguments. It allows developers to define options, commands, and aliases with TypeScript types. Using yargs simplifies argument parsing and provides built-in help menus, default values, and validation. It's ideal for building flexible CLI tools that can handle multiple options and commands without manually processing `process.argv`.
import yargs from 'yargs';

const argv = yargs(process.argv.slice(2))
  .option('name', { type: 'string', describe: 'Your name' })
  .argv;

console.log('Hello', argv.name);
      
37.3 Commander.js overview
**Commander.js** is a lightweight library for building CLI applications with commands, options, and subcommands. It allows chaining options, parsing arguments, and executing specific functions per command. In TypeScript, Commander.js can be fully typed, providing safer development and better IDE support. It's commonly used in npm CLI packages due to its simplicity and structured command handling.
import { Command } from 'commander';
const program = new Command();

program
  .option('-n, --name <string>', 'Your name')
  .parse(process.argv);

console.log('Name:', program.opts().name);
      
37.4 Parsing arguments
Parsing arguments in CLI tools is essential to convert **user input into actionable parameters**. TypeScript ensures the correct types, and libraries like yargs or Commander.js provide methods to parse, validate, and default arguments. Proper parsing allows creating flexible commands that accept flags, options, and positional arguments efficiently.
const args = process.argv.slice(2);
const firstArg = args[0];
console.log('First argument:', firstArg);
      
37.5 Input validation
Validating input ensures that CLI users provide **correct and safe data**. TypeScript types help during development, but runtime checks prevent errors during execution. Input validation can check argument types, required fields, or value ranges. Libraries like yargs simplify validation by enforcing types and defaults.
const age = Number(program.opts().age);
if(isNaN(age)) {
  console.error('Age must be a number');
  process.exit(1);
}
      
37.6 Colored console output
Colored output improves **CLI readability** by distinguishing messages, warnings, and errors. ANSI escape codes or libraries like chalk can style console messages. TypeScript ensures correct string handling, and colors help developers or users quickly understand status messages or errors.
console.log('\x1b[32m%s\x1b[0m', 'Success: CLI is working!');
console.error('\x1b[31m%s\x1b[0m', 'Error occurred!');
      
37.7 Logging best practices
Logging in CLI apps provides **debugging, monitoring, and user feedback**. Best practices include structured logs, levels (info, warning, error), and consistent formatting. TypeScript helps define log types and ensures functions are used correctly. Logging can be synchronous or asynchronous depending on performance needs.
function logInfo(message: string) {
  console.log('[INFO]', message);
}
logInfo('CLI started');
      
37.8 Async CLI tasks
Many CLI tools perform asynchronous tasks like **API calls, file reading, or network operations**. Using async/await in TypeScript ensures sequential execution, proper error handling, and readable code. Async CLI tasks improve efficiency without blocking the terminal.
import inquirer from 'inquirer';

async function runTask() {
  const answers = await inquirer.prompt([{ type: 'input', name: 'email', message: 'Enter your email:' }]);
  console.log('Email:', answers.email);
}
runTask();
      
37.9 File system CLI operations
CLI apps often interact with the file system to **read, write, or modify files**. Node.js fs module is fully supported in TypeScript with type definitions, allowing safer file operations. Ensuring correct paths and handling errors prevents crashes and data loss.
import fs from 'fs';
fs.writeFileSync('output.txt', 'CLI output using TypeScript');
      
37.10 Interactive prompts
Interactive prompts allow CLI users to provide input during execution. Libraries like **Inquirer.js** integrate well with TypeScript, offering types for prompt responses. Prompts make tools more dynamic and user-friendly by gathering runtime information without needing arguments.
import inquirer from 'inquirer';

const answers = await inquirer.prompt([{ type: 'input', name: 'username', message: 'Enter username:' }]);
console.log('Username:', answers.username);
      
37.11 Command aliases
Aliases allow users to **run commands using short names**. In Commander.js or yargs, aliases improve CLI usability by providing intuitive shorthand for frequently used commands. TypeScript ensures alias definitions are correct and avoid conflicts.
program.command('start').alias('s').action(() => console.log('Started'));
      
37.12 Subcommands
Subcommands organize CLI apps into **smaller functional units**. Each subcommand can have its own arguments and behavior. TypeScript typing allows safe parameter passing between commands and ensures clear API for CLI users.
program.command('build <project>').description('Build project').action((proj) => console.log('Building', proj));
      
37.13 Packaging CLI apps
Packaging CLI apps for npm requires **compiling TypeScript to JavaScript**, adding the `bin` field in package.json, and ensuring dependencies are included. TypeScript compilation ensures type safety before publishing.
/* package.json */
{
  "name": "my-cli",
  "version": "1.0.0",
  "bin": { "mycli": "./dist/index.js" }
}
      
37.14 Publishing to npm
Publishing involves building the CLI, compiling TypeScript, and using `npm publish`. Best practices include **semantic versioning, README docs, and tested builds**. TypeScript ensures that distributed code is type-safe.
tsc
npm publish
      
37.15 Best practices
Best practices for TypeScript CLI apps include **strong typing, input validation, error handling, structured logging, readable code, and modular architecture**. Use async/await for async tasks, colorize output, and include tests for reliability.
console.log('Always validate input, handle errors, and log messages consistently.');
      

38.1 WebSockets overview
WebSockets allow **full-duplex communication between client and server** over a single TCP connection. Unlike HTTP, WebSockets maintain a persistent connection, enabling low-latency, real-time data transfer. TypeScript can be used to type WebSocket events and messages, ensuring developers handle data structures consistently, reducing runtime errors in real-time applications such as chat apps, collaborative tools, or live dashboards.
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event: MessageEvent) => console.log('Message:', event.data);
      
38.2 Socket.IO setup
**Socket.IO** simplifies WebSocket communication, providing fallback transports and easy event handling. Using TypeScript with Socket.IO ensures **typed events and payloads**, improving developer confidence and preventing runtime issues. Setup involves installing the server and client packages, defining events, and managing connections.
import { Server } from 'socket.io';
const io = new Server(3000);

io.on('connection', socket => {
  console.log('Client connected:', socket.id);
});
      
38.3 Typing events
TypeScript allows **typing of events** sent and received through WebSockets or Socket.IO. This ensures developers know the exact structure of messages, reducing runtime errors. By defining interfaces for event payloads, applications remain predictable and maintainable, even in complex real-time systems.
interface ChatMessage { user: string; message: string; }
socket.on('chat', (msg: ChatMessage) => console.log(msg.user, msg.message));
      
38.4 Broadcasting messages
Broadcasting allows sending messages to **multiple clients simultaneously**. In Socket.IO, you can broadcast messages to all clients except the sender. TypeScript typing ensures broadcasted messages conform to defined structures, improving consistency and preventing client-side errors.
socket.broadcast.emit('chat', { user: 'Alice', message: 'Hello everyone!' });
      
38.5 Rooms and namespaces
Rooms and namespaces organize Socket.IO connections for **scoped communication**. Rooms let you group sockets, while namespaces separate event channels. TypeScript interfaces help define messages for each room, ensuring event consistency across clients and servers.
socket.join('room1');
io.to('room1').emit('notification', 'Welcome to room 1!');
      
38.6 Client-server communication
Type-safe client-server communication ensures **both sides understand message formats**. TypeScript interfaces and types enforce strict contracts, preventing data inconsistencies. Proper typing reduces runtime errors in real-time apps and simplifies debugging and maintenance.
interface Update { id: number; status: string; }
socket.emit('update', { id: 1, status: 'done' });
      
38.7 Error handling
Error handling is crucial in real-time apps to prevent crashes or inconsistent states. TypeScript helps by **typing error messages and events**, allowing developers to catch errors early. Implementing try/catch blocks and event listeners ensures reliability.
socket.on('error', (err: Error) => console.error('Socket error:', err.message));
      
38.8 Reconnection logic
Clients may disconnect due to network issues. Reconnection logic **automatically retries connections**. TypeScript types ensure reconnect events and related messages are handled safely and consistently, preventing unexpected crashes or duplicate events.
socket.io.on('reconnect_attempt', (attempt) => console.log('Reconnect attempt', attempt));
      
38.9 Real-time notifications
Real-time notifications **push updates immediately** to users. TypeScript interfaces guarantee that notification payloads are structured, enabling consistent display across clients and reducing runtime errors.
interface Notification { title: string; body: string; }
socket.emit('notify', { title: 'Alert', body: 'New message!' });
      
38.10 Chat applications
Real-time chat apps rely on **event-driven communication**. TypeScript ensures all messages and events conform to predefined types, preventing client-server mismatches. This improves maintainability and reduces bugs in chat features.
socket.on('chat', (msg: ChatMessage) => console.log(msg.user, msg.message));
      
38.11 Collaborative tools
Collaborative apps like **shared editors or whiteboards** require synchronized states. TypeScript typing ensures that all events and updates sent between clients maintain a consistent structure, enabling reliable collaboration and avoiding data conflicts.
interface CanvasUpdate { x: number; y: number; color: string; }
socket.emit('draw', { x: 10, y: 20, color: 'red' });
      
38.12 Performance considerations
Real-time apps can be **resource-intensive**. TypeScript helps by enabling developers to **profile and optimize message structures**, reduce payload sizes, and ensure type-safe operations that avoid unnecessary runtime checks. Efficient event handling prevents lag and improves scalability.
socket.emit('update', { id: 1, value: 'optimized' }); // minimal payload
      
38.13 Testing real-time apps
Testing real-time apps requires **simulating events and verifying client-server communication**. TypeScript ensures that test data conforms to real interfaces, reducing false positives or runtime issues. Unit tests and integration tests validate event handling and message correctness.
import { io } from 'socket.io-client';
const testSocket = io('http://localhost:3000');
testSocket.emit('chat', { user: 'Test', message: 'Hello' });
      
38.14 Security considerations
Security is vital in real-time apps. TypeScript typing **prevents injection or malformed data**. Implement authentication, authorization, and validate all payloads. Enforcing types reduces the chance of unexpected or malicious data causing crashes or vulnerabilities.
socket.on('update', (data: Update) => { if(!data.id) throw new Error('Invalid payload'); });
      
38.15 Best practices
Best practices include **typed events, error handling, reconnection strategies, payload optimization, and security enforcement**. TypeScript ensures consistent data handling, improves maintainability, and prevents runtime issues in complex real-time applications.
interface EventPayload { event: string; data: any; }
socket.emit('event', { event: 'ping', data: {} });
      

39.1 AWS SDK typing
Using TypeScript with the AWS SDK ensures **type safety when interacting with AWS services** such as S3, DynamoDB, and Lambda. Strong typing prevents runtime errors by validating function parameters and responses at compile-time. This improves code maintainability and makes cloud interactions more predictable.
<!-- Example: S3 bucket list using AWS SDK with TypeScript -->
import AWS from 'aws-sdk';
const s3 = new AWS.S3();
async function listBuckets(): Promise<AWS.S3.Bucket[]> {
  const result = await s3.listBuckets().promise();
  return result.Buckets || [];
}
      
39.2 Azure SDK typing
TypeScript with Azure SDK enables **type-safe access to Azure resources** like Blob Storage, Cosmos DB, and Functions. It provides autocomplete, interface validation, and reduces errors during deployment or runtime when managing cloud resources.
<!-- Example: Azure Blob Storage -->
import { BlobServiceClient } from '@azure/storage-blob';
const blobService = BlobServiceClient.fromConnectionString('AZURE_CONNECTION_STRING');
async function listContainers(): Promise<string[]> {
  const containers = [];
  for await (const container of blobService.listContainers()) {
    containers.push(container.name);
  }
  return containers;
}
      
39.3 Google Cloud SDK
Using TypeScript with Google Cloud SDK allows **strongly-typed interactions with services** like Firestore, Storage, and Pub/Sub. Type safety ensures developers handle responses correctly and prevents misconfigured API calls, especially in large cloud applications.
<!-- Example: Firestore typing -->
import { Firestore } from '@google-cloud/firestore';
const db = new Firestore();
async function getUsers(): Promise<Array<{id: string, name: string}>> {
  const snapshot = await db.collection('users').get();
  return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() as any }));
}
      
39.4 Firebase setup
Firebase with TypeScript allows developers to **initialize apps safely** and leverage autocomplete for services like Authentication, Firestore, and Realtime Database. Correct setup ensures predictable interactions with cloud resources and improves code reliability.
<!-- Example: Firebase initialization -->
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = { apiKey: 'YOUR_KEY', authDomain: 'PROJECT.firebaseapp.com' };
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
      
39.5 Firestore typing
Using TypeScript with Firestore allows **typed document reads and writes**, reducing runtime errors. Interfaces define document shapes, making queries and updates predictable and safe.
<!-- Example: Firestore typed read -->
interface User { id: string; name: string; age: number; }
import { getFirestore, collection, getDocs } from 'firebase/firestore';
const db = getFirestore();
async function getUsers(): Promise<User[]> {
  const snapshot = await getDocs(collection(db, 'users'));
  return snapshot.docs.map(doc => doc.data() as User);
}
      
39.6 Realtime Database typing
TypeScript ensures **type-safe access to Realtime Database** paths and data structures. It prevents misaligned reads/writes, making real-time updates reliable and predictable.
<!-- Example: Realtime DB typing -->
import { getDatabase, ref, get } from 'firebase/database';
const db = getDatabase();
async function getUser(id: string): Promise<{name: string}> {
  const snapshot = await get(ref(db, 'users/' + id));
  return snapshot.val();
}
      
39.7 Cloud Functions typing
TypeScript with Cloud Functions provides **strong typing for triggers, parameters, and context**, improving maintainability and reducing runtime errors in serverless functions.
<!-- Example: Firebase Function -->
import { https } from 'firebase-functions';
export const helloWorld = https.onRequest((req, res) => {
  res.send('Hello TypeScript Cloud Functions!');
});
      
39.8 Storage typing
TypeScript typing ensures **safe interactions with cloud storage**, including uploading, downloading, and managing files with correct metadata.
<!-- Example: Uploading to Firebase Storage -->
import { getStorage, ref, uploadBytes } from 'firebase/storage';
const storage = getStorage();
const fileRef = ref(storage, 'files/myFile.txt');
await uploadBytes(fileRef, new Blob(['Hello']));
      
39.9 Authentication typing
Strong typing improves **security and reliability for authentication** workflows, including login, signup, and session management in TypeScript.
<!-- Example: Firebase Auth login -->
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth();
await signInWithEmailAndPassword(auth, 'user@example.com', 'password');
      
39.10 Serverless functions
TypeScript allows **type-safe serverless functions**, ensuring correct inputs, outputs, and context handling across cloud providers.
<!-- Example: AWS Lambda with TypeScript -->
import { APIGatewayEvent } from 'aws-lambda';
export const handler = async (event: APIGatewayEvent) => {
  return { statusCode: 200, body: 'Hello Lambda' };
};
      
39.11 Deployment pipelines
TypeScript helps maintain **typed configuration in deployment pipelines**, improving automation reliability and reducing errors during cloud deployments.
<!-- Example: CI/CD pipeline step in TypeScript -->
const deploy = async () => { console.log('Deploying app...'); };
await deploy();
      
39.12 Monitoring & logging
Typed monitoring and logging code improves **traceability, error detection, and observability** in cloud applications.
<!-- Example: Logging in Cloud Functions -->
import { logger } from 'firebase-functions';
logger.info('Function executed successfully');
      
39.13 Secrets management
TypeScript helps enforce **proper handling of secrets** in cloud environments, preventing accidental exposure of API keys or credentials.
<!-- Example: Using environment variables -->
const apiKey: string = process.env.API_KEY!;
      
39.14 Event triggers
Cloud services often trigger functions on **database, storage, or messaging events**. TypeScript typing ensures the handler correctly interprets the event payload.
<!-- Example: Firestore trigger -->
import { firestore } from 'firebase-functions';
export const onUserCreate = firestore.document('users/{id}').onCreate((snap, context) => {
  console.log('User created', snap.data());
});
      
39.15 Best practices
When using TypeScript with cloud services, always **leverage interfaces, strong typing, and type-safe SDKs**, enable strict compiler options, validate inputs, and handle errors consistently. This approach improves reliability, maintainability, and security across cloud applications.
<!-- Example: Type-safe Cloud SDK usage -->
interface User { id: string; name: string; }
const users: User[] = [{ id: '1', name: 'Alice' }];
      

40.1 Advanced type manipulation
Advanced type manipulation in TypeScript involves **creating complex and reusable types** using features like mapped types, conditional types, and type inference. These allow developers to **model sophisticated data structures** and enforce compile-time rules across applications, improving safety and maintainability.
type ReadOnlyKeys<T> = { readonly [K in keyof T]: T[K] };
interface User { name: string; age: number; }
type ReadOnlyUser = ReadOnlyKeys<User>;
      
40.2 Conditional and infer types
Conditional and `infer` types let you **derive new types based on conditions** at compile-time. They provide flexibility in transforming types dynamically, enabling patterns like extracting function return types or filtering union types.
type ElementType<T> = T extends (infer U)[] ? U : T;
type Item = ElementType<string[]>; // string
      
40.3 Recursive types
Recursive types allow defining **self-referential type structures**, which are useful for tree-like or nested data structures. TypeScript checks these at compile-time to ensure consistency.
type NestedArray<T> = T | NestedArray<T>[];
const nums: NestedArray<number> = [1, [2, [3]]];
      
40.4 Template literal types
Template literal types **combine string literals with type constraints**, enabling precise string patterns for safer code. They are particularly useful for validating keys, events, or routes in TypeScript applications.
type EventName = `on${string}Event`;
const clickEvent: EventName = 'onClickEvent';
      
40.5 Module augmentation
Module augmentation allows you to **extend or enhance existing modules** without modifying original source code. This is essential when working with third-party libraries to add new types or features in a type-safe way.
import 'express';
declare module 'express' {
  interface Request { user?: string; }
}
      
40.6 Declaration merging
Declaration merging lets multiple declarations of the same interface or module **combine into a single type**, enabling flexible type extension and integration across different parts of the codebase.
interface User { name: string; }
interface User { age: number; }
const u: User = { name: 'Alice', age: 30 };
      
40.7 Type inference optimization
Optimizing type inference ensures **TypeScript automatically deduces accurate types** without excessive annotations. This reduces boilerplate while maintaining safety and readability.
const numbers = [1, 2, 3]; // inferred as number[]
const doubled = numbers.map(n => n * 2);
      
40.8 Performance tuning
In large TypeScript projects, performance tuning involves **reducing type complexity, using incremental builds, and optimizing tsconfig**. These practices improve compile times and developer productivity.
/* tsconfig.json */
{ "compilerOptions": { "incremental": true, "skipLibCheck": true } }
      
40.9 Custom compiler APIs
TypeScript exposes **compiler APIs** that allow programmatic manipulation of code, building custom linting, transformation, or analysis tools. This is crucial for advanced tooling in enterprise projects.
import * as ts from 'typescript';
const sourceFile = ts.createSourceFile('app.ts', 'const x: number = 5;', ts.ScriptTarget.Latest);
      
40.10 Large-scale project structuring
Structuring large TypeScript projects involves **modular architecture, clear folder conventions, and typed interfaces**, which ensures maintainability and reduces integration issues as teams scale.
src/
  components/
  services/
  utils/
      
40.11 Monorepos with TypeScript
Monorepos allow multiple projects or packages **to share common code**. TypeScript provides typing across packages, making it easier to manage dependencies and maintain consistency.
packages/
  core/
  web-app/
  shared-types/
      
40.12 CI/CD for TS projects
Continuous Integration and Deployment pipelines for TypeScript ensure **automatic compilation, testing, and deployment**. Type checking in CI prevents type errors from reaching production.
# Example: GitHub Actions step
- name: Compile TypeScript
  run: tsc --noEmit
      
40.13 Advanced debugging patterns
Advanced debugging leverages **source maps, typed logging, and IDE integrations**. TypeScript enables early detection of issues, improving debugging efficiency.
const data: string | null = null;
console.log(data?.toUpperCase()); // safely handled
      
40.14 Refactoring techniques
Refactoring with TypeScript is safer due to **types guiding changes**. Renaming, extracting methods, or moving modules preserves type safety and reduces runtime errors.
function add(a: number, b: number) { return a + b; }
const sum = add(2, 3);
      
40.15 Career guidance and best practices
Expert TypeScript developers focus on **strong typing, advanced patterns, performance optimization, and contributing to typed libraries**. Following best practices in large-scale projects ensures maintainable, reliable, and high-quality software.
interface Developer { name: string; expertise: string[]; }
const dev: Developer = { name: 'Alice', expertise: ['TypeScript', 'GraphQL'] };
      

41.1 Introduction to WebAssembly
WebAssembly (WASM) is a **low-level binary instruction format** for the web that allows high-performance code execution in browsers. WASM enables applications to run **near-native speed**, offering advantages for computationally intensive tasks such as games, simulations, or data processing. TypeScript developers can leverage WASM for **performance-critical components**, interacting with JavaScript seamlessly while retaining type safety.
// WASM runs in the browser alongside JS
// Example: Load a simple WASM module
      
41.2 TypeScript and WASM overview
TypeScript can integrate with WebAssembly via **AssemblyScript**, which allows writing TypeScript-like syntax that compiles to WASM. This approach enables developers to maintain **type safety, familiarity, and strong tooling** while producing high-performance modules for web and server applications.
// AssemblyScript: TS syntax to WASM
export function add(a: i32, b: i32): i32 {
  return a + b;
}
      
41.3 Setting up AssemblyScript
AssemblyScript requires **installation via npm**, configuring a project to compile TypeScript to WebAssembly. This includes creating entry points, a `tsconfig.json` adapted for AssemblyScript, and build scripts. Developers then **compile .ts files into .wasm modules** ready for web usage.
npm install --save-dev assemblyscript
npx asinit .
      
41.4 Compiling TypeScript to WASM
Compiling involves running AssemblyScript’s compiler (`asc`) to convert TypeScript into WASM binaries. Options allow **optimizing for size or speed** and generating corresponding TypeScript declaration files for type-safe imports in JS projects.
npx asc assembly/index.ts -b build/module.wasm -t build/module.wat
      
41.5 Exporting functions to JS
Functions in WASM modules can be **exported** and invoked from JavaScript. This allows **seamless integration**, where performance-intensive logic runs in WASM while the main application continues in JS/TypeScript.
export function multiply(a: i32, b: i32): i32 { return a * b; }
// JS: import and use
// const result = wasmModule.exports.multiply(2, 3);
      
41.6 Calling WASM from TypeScript
TypeScript code can **instantiate and call WASM modules** using WebAssembly APIs. Type safety is maintained using **declaration files**, ensuring proper arguments and return types.
const wasm = await WebAssembly.instantiateStreaming(fetch('module.wasm'));
const result: number = wasm.instance.exports.add(5, 10) as number;
      
41.7 Memory management in WASM
WASM modules manage memory via **linear memory buffers**, requiring developers to handle allocation, deallocation, and typed views. AssemblyScript simplifies this, but understanding memory usage ensures **performance and stability**.
let memory = new WebAssembly.Memory({ initial: 1 });
let view = new Uint8Array(memory.buffer);
      
41.8 Passing complex data structures
Transferring objects between TypeScript and WASM requires **serialization into supported types** (e.g., integers, floats, arrays). Developers can use helper functions to encode/decode structured data safely.
export function sumArray(arr: Int32Array): i32 {
  let sum: i32 = 0;
  for (let i = 0; i < arr.length; i++) sum += arr[i];
  return sum;
}
      
41.9 Performance optimization
Optimizing WASM modules involves **reducing memory copies, leveraging linear memory, minimizing function calls**, and tuning AssemblyScript compilation flags. Profiling tools help identify bottlenecks for high-performance applications.
npx asc assembly/index.ts -O3 -b build/module.wasm
      
41.10 Debugging WASM modules
Debugging includes **source maps generation, logging via console, and inspecting memory**. Tools like browser devtools support WASM debugging, allowing developers to trace and validate logic.
// Generate source maps
npx asc assembly/index.ts -g -b build/module.wasm
      
41.11 Using third-party WASM libraries
Developers can integrate **existing WASM libraries** for image processing, compression, or cryptography. TypeScript declaration files ensure type-safe interaction and better IDE support.
import * as wasmCrypto from 'wasm-crypto';
// wasmCrypto.hash(...)
      
41.12 WASM and web security
WASM operates in a **sandboxed environment**, preventing arbitrary memory access. Security best practices include **validating inputs, restricting network access**, and monitoring third-party modules.
// WASM module runs in isolated memory and execution context
      
41.13 Real-time applications with WASM
WASM can **enhance real-time applications** such as games, audio processing, or simulations by offloading computation-heavy tasks while TypeScript handles events and UI logic.
// WASM can process frames, audio, or physics calculations in real-time
      
41.14 Benchmarking TypeScript + WASM
Benchmarking involves **measuring execution time, memory usage, and throughput** for WASM modules. Comparing JS vs WASM helps identify performance gains and optimization opportunities.
console.time('wasm');
const result = wasmModule.exports.calculate();
console.timeEnd('wasm');
      
41.15 Best practices
Best practices include **using AssemblyScript for strong typing, optimizing memory, profiling, writing small modules**, and maintaining clear interfaces for JS-WASM communication.
// Keep WASM modules small and modular
// Use typed interfaces for interop
      

42.1 Introduction to ML in TS
Machine learning (ML) in TypeScript allows developers to **build intelligent applications** using type-safe code. TypeScript’s static typing helps prevent runtime errors while working with tensors, models, and predictions. You can leverage libraries like TensorFlow.js to run ML **directly in the browser or Node.js**, enabling projects like image recognition, NLP, and regression analysis with strong code reliability.
// TypeScript ensures type safety for ML pipelines
import * as tf from '@tensorflow/tfjs';
      
42.2 TensorFlow.js overview
TensorFlow.js is a **JavaScript/TypeScript library for ML**, allowing developers to define, train, and run neural networks in browsers or Node.js. Using TensorFlow.js with TypeScript provides **autocompletion, error detection, and interface definitions**, simplifying ML model development and integration.
import * as tf from '@tensorflow/tfjs';
const model = tf.sequential();
      
42.3 Creating neural networks
Neural networks in TypeScript can be created using TensorFlow.js’s **Sequential or Functional API**. TypeScript ensures layers, activation functions, and parameters are **type-checked**, reducing runtime bugs.
const model = tf.sequential();
model.add(tf.layers.dense({ units: 32, activation: 'relu', inputShape: [10] }));
model.add(tf.layers.dense({ units: 1, activation: 'linear' }));
      
42.4 Training models in browser
Models can be trained directly in browsers using **client-side data**. TypeScript ensures **tensor shapes and data types** match expected formats. Training in-browser allows **interactive demos** and privacy-preserving ML since data never leaves the client.
await model.compile({ optimizer: 'sgd', loss: 'meanSquaredError' });
await model.fit(trainingData, trainingLabels, { epochs: 50 });
      
42.5 Data preprocessing
Proper preprocessing is critical in ML pipelines. TypeScript helps **enforce data types and tensor shapes** for normalization, scaling, and feature extraction, reducing errors before feeding data into models.
const inputTensor = tf.tensor2d(data, [data.length, featuresCount]);
const normalized = inputTensor.div(tf.scalar(255));
      
42.6 Using pre-trained models
TypeScript can load **pre-trained models** from TensorFlow.js or other sources, enabling transfer learning. This accelerates development and leverages existing model weights while maintaining type safety.
const mobilenet = await tf.loadLayersModel('https://.../mobilenet/model.json');
      
42.7 Image classification projects
ML projects such as **image classification** can be built using TypeScript with TensorFlow.js. You define input shapes, preprocess images, and make predictions with type-checked outputs, reducing runtime errors.
const prediction = mobilenet.predict(imageTensor) as tf.Tensor;
      
42.8 NLP projects
TypeScript enables **natural language processing** projects like sentiment analysis or text classification. Type safety ensures tokenized input, embedding dimensions, and output labels are consistent with the model definition.
import * as tf from '@tensorflow/tfjs';
const input = tf.tensor2d(tokenizedText, [batchSize, maxLen]);
const output = model.predict(input) as tf.Tensor;
      
42.9 Regression models
TypeScript ensures **correct data types and tensor dimensions** for regression problems, predicting numeric outcomes. Strong typing reduces errors in inputs, model outputs, and training loops.
model.add(tf.layers.dense({ units: 1, activation: 'linear' }));
      
42.10 Model evaluation
TypeScript ensures evaluation metrics like **loss and accuracy** are correctly handled. Using `evaluate` and `predict` functions is type-checked to avoid runtime inconsistencies.
const evalResult = model.evaluate(testData, testLabels) as tf.Scalar[];
      
42.11 Model deployment
Trained models can be deployed **in-browser or on Node.js servers**. TypeScript ensures consistent API integration and prevents type-related deployment errors.
await model.save('localstorage://my-model');
      
42.12 Performance optimization
TypeScript assists in identifying **tensor shape mismatches and redundant operations**, enabling better GPU utilization and faster model execution. Proper typing reduces debugging time.
tf.tidy(() => {
  const result = model.predict(inputTensor) as tf.Tensor;
});
      
42.13 Integrating with Node.js backend
TypeScript enables seamless ML integration with **Node.js backends**, type-checking API endpoints, input shapes, and predictions. This ensures reliability when serving models.
import * as tf from '@tensorflow/tfjs-node';
const model = await tf.loadLayersModel('file://model/model.json');
      
42.14 Visualization of predictions
Predictions can be visualized in browser dashboards. TypeScript ensures **correct data formatting**, safe mapping to charting libraries, and reliable rendering of ML outputs.
chart.data = predictions.map(p => p.dataSync()[0]);
chart.update();
      
42.15 Best practices
Best practices include **using strong typing for tensors and models, modularizing ML pipelines, validating input/output types, and leveraging pre-trained models**. TypeScript ensures safer, maintainable, and performant machine learning applications.
// Define tensor shapes and types
// Modularize ML pipelines
// Use pre-trained models where possible
      

43.1 Introduction to web components
Web components allow developers to create **reusable, encapsulated HTML elements**. TypeScript provides type safety and interfaces for properties, methods, and events, ensuring better maintainability and reducing runtime errors. Using web components enables framework-agnostic UI components that can work with vanilla JS or frameworks like React, Angular, and Vue.
// Define a basic custom element
class MyComponent extends HTMLElement {
  constructor() {
    super();
    console.log('Component created');
  }
}
customElements.define('my-component', MyComponent);
      
43.2 Custom elements
Custom elements are **HTML elements defined by developers**. TypeScript allows defining interfaces for element attributes, methods, and events, providing strong typing and autocomplete support in editors.
class GreetingElement extends HTMLElement {
  connectedCallback() {
    this.innerHTML = '<p>Hello, TypeScript!</p>';
  }
}
customElements.define('greeting-element', GreetingElement);
      
43.3 Shadow DOM
Shadow DOM encapsulates **markup and styles** to prevent conflicts with the main document. TypeScript ensures that references and manipulations of shadow elements are type-checked, avoiding runtime errors.
class ShadowComponent extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = '<p>Inside Shadow DOM</p>';
  }
}
customElements.define('shadow-component', ShadowComponent);
      
43.4 HTML templates
HTML templates allow **defining reusable markup** without rendering it immediately. TypeScript provides **typed access to template content**, ensuring safe cloning and insertion.
const template = document.createElement('template');
template.innerHTML = '<p>Template content</p>';
const clone = template.content.cloneNode(true) as DocumentFragment;
document.body.appendChild(clone);
      
43.5 Slots and content projection
Slots let web components **accept external content**. TypeScript types the assigned elements and properties for safe interaction between component and consumer code.
class SlotComponent extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = '<slot name="content"></slot>';
  }
}
customElements.define('slot-component', SlotComponent);
      
43.6 Styling web components
Web component styles are **scoped within Shadow DOM**. TypeScript ensures typed references to style elements and helps manage dynamic style updates safely.
const style = document.createElement('style');
style.textContent = 'p { color: blue; }';
shadow.appendChild(style);
      
43.7 Event handling
TypeScript enables **typed event handling** for web components. Custom events can be strongly typed to prevent mistakes in event properties and listeners.
this.dispatchEvent(new CustomEvent('greet', { detail: 'Hello' }));
this.addEventListener('greet', (e: CustomEvent) => console.log(e.detail));
      
43.8 Lifecycle callbacks
Web components have lifecycle methods like **connectedCallback and disconnectedCallback**. TypeScript ensures that these callbacks are implemented correctly and properties used within are type-checked.
class LifeCycleComponent extends HTMLElement {
  connectedCallback() { console.log('Attached'); }
  disconnectedCallback() { console.log('Removed'); }
}
customElements.define('lifecycle-component', LifeCycleComponent);
      
43.9 Attributes and properties
Attributes are strings in HTML, whereas properties can hold typed data. TypeScript helps **define proper types** for properties, ensuring consistency between attributes and internal data.
class AttrComponent extends HTMLElement {
  private _count: number = 0;
  set count(val: number) { this._count = val; }
  get count(): number { return this._count; }
}
customElements.define('attr-component', AttrComponent);
      
43.10 Observed attributes
TypeScript allows defining **typed observed attributes** using `attributeChangedCallback`. This ensures updates are handled safely and consistently.
static get observedAttributes() { return ['count']; }
attributeChangedCallback(name: string, oldVal: string, newVal: string) {
  console.log(`${name} changed from ${oldVal} to ${newVal}`);
}
      
43.11 Inter-component communication
Components can communicate via **custom events or shared state**. TypeScript ensures events carry the correct types and payloads, avoiding runtime errors.
const event = new CustomEvent<'update'>('update', { detail: 42 });
component.dispatchEvent(event);
      
43.12 Integrating with frameworks
Web components can be used with React, Angular, or Vue. TypeScript ensures **typed props, events, and method calls**, making integration seamless and reducing bugs.
// Using a web component in React
<my-component count={10} />
      
43.13 Using libraries with TS
Third-party libraries can interact with web components. TypeScript provides **typed interfaces** for libraries, ensuring safe usage of component APIs.
import { LitElement, html } from 'lit';
class MyLit extends LitElement {
  render() { return html`<p>Lit component</p>`; }
}
      
43.14 Testing web components
Testing requires **typed component properties, events, and lifecycle methods**. TypeScript reduces runtime errors and improves test reliability.
const component = document.createElement('my-component') as HTMLElement;
component.setAttribute('count', '5');
      
43.15 Best practices
Use strong typing for attributes, properties, events, and slots. Encapsulate styles using Shadow DOM, leverage templates for reusable markup, and integrate properly with frameworks. TypeScript ensures **safe, maintainable, and reusable web components**.
// Define types for props and events
// Encapsulate styles
// Use templates for reusable content
      

44.1 Introduction to PWAs
Progressive Web Apps (PWAs) are web applications that provide **native app-like experiences**. They work offline, support push notifications, and can be installed on a user’s home screen. TypeScript enhances PWAs by providing **strong typing for service workers, caching APIs, and IndexedDB**, reducing runtime errors and improving maintainability.
// Simple TypeScript function to check PWA support
function isPWASupported(): boolean {
  return 'serviceWorker' in navigator;
}
console.log(isPWASupported());
      
44.2 Service workers setup
Service workers enable PWAs to **intercept network requests, manage caching, and handle background tasks**. TypeScript ensures proper typing for fetch events and caches, improving code safety.
navigator.serviceWorker.register('/sw.js')
  .then((registration) => console.log('SW registered', registration))
  .catch((error) => console.error('SW registration failed', error));
      
44.3 Caching strategies
PWAs use caching strategies like **cache-first, network-first, or stale-while-revalidate**. TypeScript allows **typed access to CacheStorage APIs**, ensuring predictable caching behavior.
self.addEventListener('fetch', (event: FetchEvent) => {
  event.respondWith(caches.match(event.request) || fetch(event.request));
});
      
44.4 Offline support
Offline support allows apps to work **without network connectivity**. Using TypeScript ensures that **fetch responses and cache interactions are type-safe**, reducing runtime errors in offline scenarios.
if (!navigator.onLine) {
  console.log('You are offline');
}
      
44.5 Web App Manifest
The Web App Manifest defines **app icons, name, theme color, and display modes**. TypeScript helps validate manifest JSON structure before deployment.
const manifest: { name: string; short_name: string; start_url: string; display: string } = {
  name: 'My PWA',
  short_name: 'PWA',
  start_url: '/',
  display: 'standalone'
};
      
44.6 Push notifications
Push notifications allow PWAs to **engage users with timely messages**. TypeScript types the notification payload and service worker events, ensuring consistent data handling.
self.addEventListener('push', (event: PushEvent) => {
  const data = event.data?.text() ?? 'No payload';
  event.waitUntil(self.registration.showNotification('PWA Notification', { body: data }));
});
      
44.7 Background sync
Background sync enables PWAs to **perform deferred network requests** when the connection is restored. TypeScript ensures proper event and promise handling.
self.addEventListener('sync', (event: SyncEvent) => {
  if (event.tag === 'sync-data') {
    event.waitUntil(syncData());
  }
});

async function syncData() { console.log('Syncing data'); }
      
44.8 App shell model
The app shell model separates **static UI** from dynamic content for fast load. TypeScript provides type safety for app shell rendering and caching logic.
const shell = document.getElementById('app-shell') as HTMLElement;
shell.innerHTML = '<header>Header</header><main></main>';
      
44.9 IndexedDB usage
IndexedDB stores structured data **offline**. TypeScript allows **typed access to database schema and transactions**, ensuring predictable read/write operations.
const request = indexedDB.open('pwa-db', 1);
request.onsuccess = (event) => console.log('DB opened', event);
      
44.10 Updating service workers
Service workers need proper **update strategies**. TypeScript ensures **typed registration and update detection**, avoiding unintentional caching issues.
navigator.serviceWorker.getRegistrations()
  .then((regs) => regs.forEach((r) => r.update()));
      
44.11 Performance optimization
Performance is crucial for PWAs. TypeScript helps identify **slow APIs and enforce correct typing**, optimizing caching, network requests, and rendering.
console.time('load');
// load app shell and resources
console.timeEnd('load');
      
44.12 Security considerations
PWAs must enforce **HTTPS, content security policies, and secure storage**. TypeScript ensures correct data handling and prevents runtime injection errors.
if (location.protocol !== 'https:') console.warn('PWA should run over HTTPS');
      
44.13 Lighthouse auditing
Lighthouse audits measure **PWA performance, accessibility, best practices, and SEO**. TypeScript can help generate typed configuration files for automation scripts.
const config = { extends: 'lighthouse:default' };
      
44.14 Deploying PWAs
Deployment involves **static hosting, service worker registration, and manifest inclusion**. TypeScript ensures that deployment scripts and APIs are used safely and correctly.
import * as fs from 'fs';
fs.copyFileSync('manifest.json', 'dist/manifest.json');
      
44.15 Best practices
Use **strong typing for service workers, caching, IndexedDB, and notifications**. Follow the app shell model, implement offline support, optimize performance, and secure PWA resources. TypeScript ensures maintainable, robust, and predictable behavior.
// Type interfaces for SW events
// Typed IndexedDB interactions
// Typed caching and notifications
      

45.1 HTTP/HTTPS requests
TypeScript enhances network requests by providing **typed request parameters, headers, and response structures**. This ensures safer API consumption and better developer tooling, reducing runtime errors.
async function fetchData(url: string): Promise<any> {
  const response = await fetch(url);
  return await response.json();
}
      
45.2 WebSockets advanced patterns
Advanced WebSocket usage includes **reconnection strategies, message batching, and typed events**. TypeScript ensures **strict typing of messages and event handlers**, enhancing reliability in real-time communication.
const ws = new WebSocket('wss://example.com');
ws.onmessage = (event: MessageEvent<string>) => console.log(event.data);
      
45.3 SSE (Server-Sent Events)
SSE allows **server-to-client streaming**. TypeScript types event data and ensures **typed event listeners**, making it easier to handle streaming JSON or text payloads.
const evtSource = new EventSource('/events');
evtSource.onmessage = (event: MessageEvent<string>) => console.log(event.data);
      
45.4 GraphQL subscriptions
GraphQL subscriptions enable **real-time updates**. TypeScript types subscription queries and response payloads, ensuring consistency across clients and reducing errors in handling dynamic data.
import { gql } from '@apollo/client';
const SUBSCRIBE_MESSAGES = gql`subscription { messageAdded { id text } }`;
      
45.5 WebRTC introduction
WebRTC enables **peer-to-peer audio, video, and data communication**. TypeScript types connection objects, media streams, and data channels for robust and safe real-time applications.
const pc = new RTCPeerConnection();
const stream: MediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
pc.addTrack(stream.getTracks()[0], stream);
      
45.6 Peer-to-peer connections
Peer-to-peer connections allow **direct client communication without server relay**. TypeScript ensures the **correct typing of signaling messages, ICE candidates, and connections**, avoiding runtime mismatches.
pc.onicecandidate = (event: RTCPeerConnectionIceEvent) => console.log(event.candidate);
      
45.7 Media streams typing
TypeScript allows **typed access to audio and video tracks**, ensuring developers manage streams safely, manipulate track properties, and avoid errors during recording or playback.
const audioTrack: MediaStreamTrack = stream.getAudioTracks()[0];
audioTrack.enabled = true;
      
45.8 Data channel management
Data channels enable **binary or textual real-time messaging**. TypeScript ensures **typed message sending and receiving**, supporting consistent P2P communication.
const dc = pc.createDataChannel('chat');
dc.onmessage = (event: MessageEvent<string>) => console.log(event.data);
      
45.9 Secure communication
Security in networking involves **HTTPS, WSS, and encrypted data channels**. TypeScript types certificates, headers, and payloads, ensuring secure handling of sensitive information.
const secureUrl: string = 'wss://secure.example.com';
const ws = new WebSocket(secureUrl);
      
45.10 Retry and backoff strategies
Network reliability improves with **retry and exponential backoff**. TypeScript types retry logic functions and delays, preventing incorrect implementation and runtime errors.
function retry(fn: () => Promise<any>, retries: number) { /* ... */ }
      
45.11 Network error handling
Proper error handling ensures **graceful recovery from failed requests**. TypeScript types error objects, enabling predictable responses and logging.
try {
  const data = await fetchData('/api');
} catch (error: unknown) {
  console.error(error);
}
      
45.12 Fetch API advanced usage
Advanced fetch usage includes **custom headers, abort signals, and streaming**. TypeScript types requests, responses, and options for safer API interactions.
const controller = new AbortController();
fetch('/api', { signal: controller.signal });
controller.abort();
      
45.13 Custom headers and authentication
TypeScript ensures **typed authentication tokens and headers**, reducing errors when sending JWTs or API keys.
const headers: HeadersInit = { Authorization: 'Bearer TOKEN' };
fetch('/api', { headers });
      
45.14 CORS management
Managing CORS correctly is crucial for **cross-origin requests**. TypeScript types fetch requests and responses, helping avoid runtime CORS issues.
fetch('https://api.example.com', { mode: 'cors' });
      
45.15 Best practices
Follow **typed network requests, secure protocols, error handling, and backoff strategies**. TypeScript ensures safer, maintainable, and predictable network operations for advanced real-time and distributed applications.
// Typed fetch requests
// WebSocket event typing
// Retry/backoff strategies
      

46.1 Introduction to blockchain
Blockchain is a **distributed ledger technology** where transactions are recorded in a **decentralized and immutable way**. TypeScript can be used to interact with blockchain APIs, smart contracts, and DApps with type safety and enhanced tooling.
interface Transaction {
  from: string;
  to: string;
  amount: number;
  timestamp: Date;
}
      
46.2 Smart contracts overview
Smart contracts are **self-executing programs** on a blockchain that automate agreements. TypeScript can type interactions with these contracts, ensuring safety in sending transactions or reading contract data.
interface Contract {
  address: string;
  abi: any[];
}
      
46.3 TypeScript with Ethereum
Ethereum is a **popular blockchain for smart contracts**. TypeScript provides typed interfaces for contract methods, events, and transaction objects, reducing runtime errors in DApps.
const ethAddress: string = '0x123...abc';
      
46.4 Using web3.js
Web3.js allows interaction with Ethereum nodes. TypeScript ensures **typed RPC calls, contract methods, and event listeners**, improving developer experience.
import Web3 from 'web3';
const web3: Web3 = new Web3('https://mainnet.infura.io/v3/YOUR_KEY');
      
46.5 Connecting to networks
TypeScript ensures **typed network connections** (mainnet, testnets) with Web3.js or ethers.js, reducing mistakes in RPC URL or provider configurations.
web3.setProvider(new Web3.providers.HttpProvider('https://ropsten.infura.io'));
      
46.6 Reading blockchain data
Reading blocks, transactions, or events requires **typed calls**. TypeScript ensures **correct data structures** and reduces runtime parsing errors.
const blockNumber: number = await web3.eth.getBlockNumber();
      
46.7 Writing transactions
TypeScript types help safely construct transactions including **from/to addresses, gas, and value**, ensuring valid Ethereum operations.
const txHash: string = await web3.eth.sendTransaction({
  from: '0xabc...',
  to: '0xdef...',
  value: web3.utils.toWei('1', 'ether')
});
      
46.8 Event listeners
Blockchain events can trigger client-side actions. TypeScript types **events and payloads**, ensuring safe real-time updates from smart contracts.
contract.events.Transfer((error, event) => console.log(event.returnValues));
      
46.9 Type-safe contract interaction
TypeScript interfaces map **smart contract methods and events**, ensuring developers call contracts with correct types and arguments.
interface MyToken {
  transfer(to: string, amount: number): Promise<boolean>;
}
      
46.10 Unit testing contracts
Unit testing smart contracts with TypeScript ensures **typed inputs and expected outputs**, reducing errors in transaction logic and state changes.
await myToken.transfer('0xdef...', 100);
      
46.11 Handling gas fees
Transactions require **gas estimation**. TypeScript ensures **typed gas values** and safe computations for sending Ethereum transactions.
const gasEstimate: number = await myToken.methods.transfer('0xdef...', 100).estimateGas();
      
46.12 Security considerations
TypeScript helps **avoid common pitfalls** such as sending to wrong addresses, replay attacks, or improper state handling by providing strict type checks.
// Type-safe address validation
const isValid: boolean = web3.utils.isAddress('0xabc...');
      
46.13 NFT development
NFTs are **unique tokens**. TypeScript types **metadata, ownership, and contract calls** to create, transfer, or query NFTs safely.
interface NFT {
  tokenId: number;
  owner: string;
  metadataURI: string;
}
      
46.14 DeFi integrations
TypeScript is used in **DeFi DApps** to ensure **typed interactions with lending protocols, swaps, and liquidity pools**, reducing transaction errors.
await lendingPool.deposit('0xabc...', 10);
      
46.15 Best practices
Follow **typed interactions, secure transactions, testing, and gas optimization**. TypeScript ensures safer and maintainable blockchain applications for both NFTs and DeFi projects.
// Typed smart contract methods
// Typed events
// Gas fee management
      

47.1 Introduction to micro frontends
Micro frontends split a large frontend into **smaller, independently deployable applications**. TypeScript ensures **type safety across modules** and reduces integration issues when multiple teams work on separate frontend parts.
interface MicroFrontend {
  name: string;
  url: string;
  mount: () => void;
}
      
47.2 Architecture overview
Micro frontend architecture divides a monolithic frontend into **distinct modules**, each with **its own build, deployment, and lifecycle**. TypeScript helps define contracts and interfaces for **communication and shared data**.
const mfeRegistry: MicroFrontend[] = [
  { name: 'app1', url: '/app1.js', mount: () => console.log('Mounted app1') },
];
      
47.3 Module federation
Module federation allows **dynamic loading of remote modules** at runtime. TypeScript ensures **typed imports and exports** between host and remote applications, preventing runtime type errors.
import { Header } from 'app1/Header';
      
47.4 Isolated build processes
Each micro frontend can have its **own build pipeline**, reducing interdependencies. TypeScript ensures **consistent type definitions** even when builds are separate.
// Each app has its own tsconfig.json and build script
      
47.5 Communication between apps
Micro frontends communicate via **events, shared state, or API calls**. TypeScript types event payloads and shared interfaces, avoiding integration issues.
const eventPayload: { userId: number; action: string } = { userId: 1, action: 'login' };
window.dispatchEvent(new CustomEvent('userAction', { detail: eventPayload }));
      
47.6 Shared state management
Shared state can be handled with **RxJS, Redux, or global stores**. TypeScript ensures state shape consistency across micro frontends.
interface GlobalState { user: string; token: string; }
const state: GlobalState = { user: 'Alice', token: 'abc123' };
      
47.7 Routing across micro frontends
Routing needs **coordinated navigation** between apps. TypeScript types **route parameters and paths** to ensure safe transitions.
interface Route { path: string; component: () => void; }
      
47.8 Lazy loading micro frontends
Lazy loading allows **on-demand module fetching**, improving performance. TypeScript helps **type-check remote module APIs** before use.
const loadApp = async () => { const module = await import('app1/Header'); module.mount(); }
      
47.9 Deployment strategies
Deploy each micro frontend **independently**, reducing downtime. TypeScript ensures **versioned interfaces** remain compatible across deployments.
// CI/CD pipeline deploys each app separately
      
47.10 Testing micro frontends
TypeScript allows **unit, integration, and E2E tests** across micro frontends by typing inputs, outputs, and event payloads, ensuring consistency.
test('App1 header mounts', () => { expect(Header.mount).not.toThrow(); });
      
47.11 Versioning considerations
Micro frontends may evolve independently. TypeScript interfaces and versioned types ensure **backward compatibility** and safe integration.
interface App1V2 { newProp: string; }
      
47.12 Performance monitoring
Monitor **loading times, memory, and network usage** for each micro frontend. TypeScript types analytics events and metrics for consistency.
const metric: { loadTime: number; app: string } = { loadTime: 120, app: 'app1' };
      
47.13 Security practices
Ensure **isolated execution, CSP headers, and safe communication**. TypeScript helps prevent runtime errors that could cause vulnerabilities.
// Validate messages before processing
      
47.14 Using TypeScript in multiple apps
TypeScript allows sharing **types, interfaces, and utility functions** across micro frontends, maintaining consistency.
export interface SharedUser { id: number; name: string; }
      
47.15 Best practices
Follow **typed interfaces, independent builds, event validation, lazy loading, and CI/CD deployment**. TypeScript ensures maintainable and scalable micro frontend projects.
// Type-safe communication
// Independent deployments
// Shared type definitions
      

48.1 Introduction to IoT
The Internet of Things (IoT) connects **physical devices to the internet**, enabling data collection and remote control. TypeScript enhances **type safety** and maintainability when developing IoT applications, reducing runtime errors in sensor reading, device control, and communication logic.
interface Sensor { id: string; value: number; timestamp: Date; }
      
48.2 Node.js with IoT devices
Node.js is often used for **server-side IoT applications** due to its **event-driven nature**. TypeScript helps manage **typed data streams and device interactions** with clear interfaces and predictable behavior.
import { readSensor } from 'iot-lib';
const temperature: number = readSensor('tempSensor');
      
48.3 TypeScript SDKs for devices
Many IoT devices provide **TypeScript SDKs** for control and data collection. Using TS ensures **type-safe interactions** with device APIs, avoiding invalid commands or misinterpreted data.
import { LED } from 'iot-device-sdk';
const led: LED = new LED('led1');
      
48.4 Reading sensor data
TypeScript allows **strongly typed sensor readings**, ensuring consistent data structures for temperature, humidity, or motion sensors. This reduces runtime errors in data processing and analytics.
const sensorData: Sensor = { id: 's1', value: 42, timestamp: new Date() };
      
48.5 Controlling actuators
Actuators like motors or LEDs respond to commands. TypeScript helps define **typed command structures**, preventing invalid operations and ensuring safe device control.
interface ActuatorCommand { id: string; action: string; }
const command: ActuatorCommand = { id: 'motor1', action: 'start' };
      
48.6 Communication protocols (MQTT, HTTP)
IoT devices communicate via **MQTT, HTTP, or CoAP**. TypeScript ensures **typed payloads**, making data transmission reliable and easier to debug.
const mqttPayload: { topic: string; message: string } = { topic: 'sensor/1', message: '42' };
      
48.7 Real-time data streaming
IoT applications often require **real-time data streams**. TypeScript helps manage event streams with **typed events**, reducing errors in dashboard updates and analytics pipelines.
import { Observable } from 'rxjs';
const dataStream: Observable<Sensor> = getSensorStream();
      
48.8 Data visualization
Visualizing IoT data requires **structured and typed inputs**. TypeScript ensures charts and graphs correctly interpret sensor values, avoiding runtime exceptions.
interface ChartData { timestamp: Date; value: number; }
const chartData: ChartData[] = [ { timestamp: new Date(), value: 42 } ];
      
48.9 Edge computing concepts
Edge computing processes data **locally on devices** before sending it to the cloud. TypeScript ensures **typed data flows**, helping maintain consistency between edge and cloud systems.
function preprocessData(data: Sensor[]): Sensor[] { return data.map(d => ({ ...d, value: d.value * 1.1 })); }
      
48.10 Security in IoT
IoT security is critical due to **network exposure**. TypeScript can **type-check authentication tokens and secure messages**, preventing misuse and ensuring secure communication between devices.
interface AuthToken { token: string; expires: Date; }
      
48.11 Error handling and logging
IoT devices may fail or disconnect. TypeScript ensures **typed error structures** for logging, allowing consistent monitoring and debugging.
try { readSensor('temp1'); } catch (err: Error) { console.error(err.message); }
      
48.12 Integration with cloud services
IoT apps often send data to **AWS, Azure, or Firebase**. TypeScript enforces **typed cloud APIs**, reducing runtime errors during integration and ensuring proper payloads.
import { uploadData } from 'cloud-sdk';
uploadData<Sensor>(sensorData);
      
48.13 Automation scripts
Automation scripts control devices based on **sensor inputs or schedules**. TypeScript ensures scripts handle devices and data consistently.
if(sensorData.value > 50) actuator.send({ action: 'stop' });
      
48.14 Testing IoT devices
Testing ensures **sensors and actuators** work correctly. TypeScript provides **typed mocks and simulations** for safe testing before deployment.
const mockSensor: Sensor = { id: 'mock', value: 25, timestamp: new Date() };
      
48.15 Best practices
Follow **typed interfaces, secure communication, structured logging, edge processing, and proper cloud integration**. TypeScript ensures maintainable, scalable, and safe IoT solutions.
// Strongly typed device interfaces
// Real-time streams with type safety
// Secure authentication and logging
      

49.1 Introduction to data visualization
Data visualization is the graphical representation of data to communicate insights clearly. TypeScript ensures **type safety for datasets, chart configurations, and dynamic updates**, making visualizations predictable and easier to maintain.
interface DataPoint { x: number; y: number; }
const sampleData: DataPoint[] = [ { x: 1, y: 10 }, { x: 2, y: 20 } ];
      
49.2 D3.js with TypeScript
D3.js is a powerful library for **custom data-driven visualizations**. TypeScript helps by providing **typed selections, scales, and data joins**, reducing runtime errors and improving editor support.
import * as d3 from 'd3';
const svg = d3.select<SVGSVGElement, unknown>('#chart');
      
49.3 Chart.js integration
Chart.js allows easy **chart creation** like line, bar, and pie charts. TypeScript ensures **typed datasets, options, and plugins**, enhancing maintainability in large projects.
import { Chart, ChartConfiguration } from 'chart.js';
const config: ChartConfiguration = { type: 'bar', data: { labels: ['A','B'], datasets: [{ data: [10,20] }] } };
      
49.4 Plotly.js integration
Plotly.js enables **interactive and advanced visualizations**. TypeScript typing supports **traces, layout, and event handling**, reducing errors in interactive dashboards.
import Plotly from 'plotly.js-dist';
const trace = { x: [1,2], y: [10,20], type: 'scatter' };
Plotly.newPlot('plot', [trace]);
      
49.5 Creating bar charts
Bar charts display **categorical data** visually. Using TypeScript, you can ensure **typed labels and datasets** for consistent rendering.
const barData: { labels: string[]; values: number[] } = { labels: ['Jan','Feb'], values: [50, 75] };
      
49.6 Line and area charts
Line and area charts show **trends over time**. TypeScript ensures **type-safe arrays and configuration objects** for dynamic updates.
const lineData: { x: number[]; y: number[] } = { x: [1,2,3], y: [10,15,20] };
      
49.7 Pie and donut charts
Pie charts represent **proportions**. TypeScript helps maintain **consistent data structures** to avoid rendering errors.
const pieData: { label: string; value: number }[] = [ { label: 'A', value: 30 }, { label: 'B', value: 70 } ];
      
49.8 Scatter plots
Scatter plots show **relationships between two variables**. TypeScript ensures **typed coordinates** for all data points.
const scatterPoints: { x: number; y: number }[] = [ { x: 1, y: 5 }, { x: 2, y: 10 } ];
      
49.9 Heatmaps
Heatmaps visualize **data intensity**. TypeScript typing ensures proper **matrix structures** for grid data.
const heatmapData: number[][] = [ [1,2], [3,4] ];
      
49.10 Interactive visualizations
Interactive charts allow **hover, click, and zoom interactions**. TypeScript ensures **typed event callbacks** for predictable behavior.
svg.on('click', (event, d: DataPoint) => console.log(d.x, d.y));
      
49.11 Animations in charts
Animations enhance **user engagement**. TypeScript helps maintain **typed transitions and durations** to prevent rendering errors.
d3.selectAll('rect').transition().duration(1000).attr('height', 50);
      
49.12 Data binding best practices
Typed data binding ensures **charts update predictably** with new datasets. TypeScript reduces runtime errors in DOM manipulation and chart updates.
const updateData = (newData: DataPoint[]) => { /* update chart */ };
      
49.13 Responsive charts
Responsive charts adapt to **screen size**. TypeScript ensures **typed dimensions and resize events** for smooth behavior.
window.addEventListener('resize', () => { /* adjust chart size */ });
      
49.14 Exporting visualizations
Export charts as **images or PDFs**. TypeScript ensures **typed export functions** for predictable output.
const exportChart = (format: 'png' | 'pdf') => { /* export logic */ };
      
49.15 Best practices
Follow **typed data structures, clear chart configurations, interactive callbacks, responsive design, and maintainable code**. TypeScript ensures reliability, scalability, and maintainability in visualization projects.
// Typed datasets
// Responsive and interactive charts
// Consistent data structures
      

50.1 Designing large-scale TS projects
Designing large-scale TypeScript projects requires **modularity, clear structure, and type safety**. TypeScript enforces consistency across modules, making maintenance and collaboration easier. By defining interfaces, types, and module boundaries, you ensure scalability and reduce runtime errors.
interface IUserService { getUser(id: number): Promise<User>; }
class UserService implements IUserService { /* implementation */ }
      
50.2 Monorepos vs multiple repos
Monorepos centralize multiple projects in a **single repository**, improving code sharing and versioning. Multiple repos separate concerns but can complicate dependency management. TypeScript supports both by providing **typed references across packages**.
// tsconfig.json for monorepo with references
{
  "references": [{ "path": "../shared" }]
}
      
50.3 Modular architecture
Modular architecture divides a project into **independent, reusable modules**. TypeScript allows **strictly typed interfaces and exports**, ensuring modules interact predictably.
export interface IAuth { login(user: string): boolean; }
export class Auth implements IAuth { /* ... */ }
      
50.4 Layered architecture
Layered architecture separates concerns into **presentation, business, and data layers**. TypeScript types enforce contracts between layers for **robust, maintainable code**.
class UserController { constructor(private service: UserService) {} }
      
50.5 Domain-driven design
Domain-driven design (DDD) models software around **business domains**. TypeScript enforces **domain entity types, value objects, and aggregates** for clarity and correctness.
class Order { id: string; total: number; constructor(id: string, total: number) { this.id = id; this.total = total; } }
      
50.6 Dependency injection patterns
Dependency injection (DI) promotes **decoupled architecture**. TypeScript interfaces define contracts, enabling **safe DI containers and testing**.
class AppContainer { getUserService(): IUserService { return new UserService(); } }
      
50.7 Service-oriented design
Service-oriented design structures applications as **independent services**. TypeScript typing ensures **typed service contracts**, easing integration and communication.
interface IEmailService { send(email: string): Promise<void>; }
      
50.8 Event-driven architecture
Event-driven architecture uses **events to trigger behavior**. TypeScript ensures **typed events and payloads**, preventing runtime mismatches.
interface IUserCreatedEvent { userId: number; name: string; }
eventBus.emit('userCreated', { userId: 1, name: 'Alice' } as IUserCreatedEvent);
      
50.9 Logging and monitoring strategies
Structured logging and monitoring are critical. TypeScript allows **typed log messages and metrics**, enabling consistent reporting and error tracking.
interface LogMessage { level: 'info' | 'error'; message: string; timestamp: Date; }
      
50.10 CI/CD pipelines
Continuous integration and deployment automate builds, tests, and releases. TypeScript ensures **type-safe build scripts and checks** in the CI/CD pipeline.
// Example: GitHub Actions workflow
name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install
        run: npm install
      - name: Build
        run: npm run build
      
50.11 Code quality standards
Maintainability is key in enterprise projects. TypeScript enforces **consistent types, interfaces, and linting rules**, promoting readable, reliable code.
/* ESLint + TSConfig setup */
{
  "extends": ["eslint:recommended","plugin:@typescript-eslint/recommended"]
}
      
50.12 Security auditing
Security auditing ensures enterprise systems are **resilient to attacks**. TypeScript helps by **preventing type-related vulnerabilities** and catching unsafe patterns at compile time.
if (!input) throw new Error('Invalid input'); // compile-time checked
      
50.13 Documentation practices
Document modules, interfaces, and services clearly. TypeScript types provide **self-documenting code**, enhancing maintainability.
/**
 * Returns user by ID
 * @param id User ID
 */
function getUser(id: number): User { /* ... */ }
      
50.14 Scaling strategies
Scaling large TS projects involves **modular code, monorepos, CI/CD, and service orchestration**. TypeScript ensures **typed boundaries** to maintain consistency at scale.
// Example: splitting project into packages with references
{
  "references": [{ "path": "./packages/core" }, { "path": "./packages/ui" }]
}
      
50.15 Best enterprise practices
Follow **modular design, strict typing, layered architecture, DI, event-driven patterns, logging, CI/CD, security, and proper documentation**. TypeScript provides **robust type safety, maintainability, and scalability** for enterprise-grade applications.
// Type-safe modules, DI, events, logging, CI/CD, documentation