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);
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);
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
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"));
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
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
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
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
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);
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
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
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);
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
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
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;
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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, Majid3.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: 153.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: 303.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 F3.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, Majid3.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: 103.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: 253.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: 253.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, Majid3.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: 103.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: 103.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 identity3.14 Async functions:(value: T): T { return value; } console.log(identity ("Hello")); // Output: Hello console.log(identity (123)); // Output: 123
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
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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
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 };
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
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
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); } }
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()); } }
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(); } }
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();
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
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
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;
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"
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"
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] };
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
// 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
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
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
interface Add { (a: number, b: number): number; } const sum: Add = (x, y) => x + y; console.log(sum(5, 10));6.5 Indexable Types
interface StringArray { [index: number]: string; } const fruits: StringArray = ["Apple", "Banana"]; console.log(fruits[0]);6.6 Extending Interfaces
interface Person { name: string; } interface Employee extends Person { employeeId: number; } const emp: Employee = { name: "Ali", employeeId: 101 };6.7 Implementing Interfaces in Classes
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
interface Counter { (start: number): string; interval: number; reset(): void; } function getCounter(): Counter { let counter =6.9 Interfaces vs Typesfunction(start: number) { return `Count: ${start}`; }; counter.interval = 123; counter.reset = () => { console.log("Reset"); }; return counter; } const c = getCounter(); console.log(c(10)); c.reset();
interface A { name: string; } type B = { age: number; } const obj: A & B = { name: "Ali", age: 30 };6.10 Interface Merging
interface User { name: string; } interface User { age: number; } const u: User = { name: "Ali", age: 25 };6.11 Generics in Interfaces
interface Box6.12 Polymorphic Interfaces{ content: T; } const stringBox: Box = { content: "Hello" }; const numberBox: Box = { content: 123 };
interface Response6.13 Best Practices with Interfaces{ data: T; success: boolean; } const res: Response = { data: "OK", success: true }; const res2: Response = { data: 200, success: true };
// Example: clean interface interface Config { readonly version: string; optional?: boolean; }6.14 Real-World Examples
interface ApiResponse6.15 Debugging Interface Errors{ data: T; status: number; } const response: ApiResponse = { data: "Success", status: 200 };
interface User { name: string; age: number; } const u: User = { name: "Ali" }; // Error: missing age
<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"
<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"]
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
interface KeyValue{ key: K; value: V; } const kv: KeyValue = { key: "age", value: 30 }; console.log(kv); // { key: "age", value: 30 }
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
function createArray(length: number, value: T): T[] { return Array(length).fill(value); } console.log(createArray(3, "a")); // ["a","a","a"]
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" }
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 }
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"
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"]
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"
type ReadonlyUser= { readonly [P in keyof T]: T[P] }; const user: ReadonlyUser<{ name: string }> = { name: "Alice" }; // user.name = "Bob"; // Error: cannot assign
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
// Generics don't affect runtime function echo(value: T): T { return value; } console.log(echo(123)); // 123
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
// example of a simple module: mathUtils.ts export const pi = 3.14; export function square(x: number): number { return x * x; }
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;
export function greet(name: string): string { return `Hello, ${name}`; } export function sum(a: number, b: number): number { return a + b; }
export class Person { constructor(public name: string, public age: number) {} describe(): string { return `${this.name} is ${this.age} years old.`; } }
// 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"
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
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
* 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
export { pi, square } from './mathUtils';
namespace Geometry { export function circleArea(radius: number): number { return Math.PI * radius * radius; } } console.log(Geometry.circleArea(3)); // 28.27
namespace Geometry { export namespace Shapes { export function squareArea(side: number): number { return side * side; } } } console.log(Geometry.Shapes.squareArea(4)); // 16
namespace Library { export function read() { console.log("Read"); } } namespace Library { export function write() { console.log("Write"); } } Library.read(); // "Read" Library.write(); // "Write"
// Modules are file-based; namespaces are internal
import * as _ from 'lodash'; console.log(_.shuffle([1,2,3])); // [2,1,3] (shuffled array)
// Example: small focused module export function logMessage(msg: string) { console.log(msg); }
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
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"] }
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 }
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" } }
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 } }
{ "compilerOptions": { "sourceMap": true } }
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" } }
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"] }
incremental
option and generates a .tsbuildinfo
file to track previous compilations.
{ "compilerOptions": { "incremental": true } }
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
let num: number = "string"; // Error: Type 'string' is not assignable to type 'number'
let value: any; console.log(value.notExist); // Warning or runtime error depending on compiler options
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
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/*"] } } }
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" } }
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!"
function sumAll(...nums: number[]): number { return nums.reduce((acc, val) => acc + val, 0); } console.log(sumAll(1,2,3,4)); // 10
...
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]
function greet({name, age}: {name: string; age: number}) { console.log(`Hello ${name}, age ${age}`); } greet({name:"Alice", age:30}); // Hello Alice, age 30
?.
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
??
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
function printId(id: string | number) { if (typeof id === "string") console.log(id.toUpperCase()); else console.log(id + 100); } printId("abc"); // ABC printId(50); // 150
interface StringMap { [key: string]: string; } const dict: StringMap = { a: "apple", b: "banana" }; console.log(dict["a"]); // apple
?:
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
&&
, ||
, !
) 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
?
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
function tag(strings: TemplateStringsArray, ...values: any[]) { return strings[0] + values.join("-"); } console.log(tag`Hello ${"World"} ${123}`); // Hello World-123
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]
function fetchData(callback: (data: string) => void) { setTimeout(() => callback("Fetched data"), 1000); } fetchData((result) => console.log(result)); // Fetched data
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]
// 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
// 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
// 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
// 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 assign11.5 Parameter Decorators
// 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
// 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
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
/* tsconfig.json */ { "compilerOptions": { "experimentalDecorators": true } }11.9 Angular Decorators Overview
// Angular example @Component({ selector: 'app-root', template: '<h1>Hello Angular</h1>' }) export class AppComponent {}11.10 NestJS Decorators Overview
// NestJS example @Controller('users') export class UserController { @Get() findAll() { return ['user1','user2']; } }11.11 Combining Multiple Decorators
function decoA(target: any) { console.log("A"); } function decoB(target: any) { console.log("B"); } @decoA @decoB class Demo {} // Output: B, then A11.12 Using Decorators for Logging
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
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"; // Error11.14 Debugging Decorators
function debug(target: any, key: string) { console.log(`Decorator applied on ${key}`); } class Demo { @debug test() {} }11.15 Best Practices
// Example: Clean reusable decorator function ReadOnly(target: any, key: string) { Object.defineProperty(target, key, { writable: false }); } class Config { @ReadOnly version = "1.0"; }
try { throw new Error('Something went wrong'); } catch (error) { console.log('Caught:', error.message); } finally { console.log('Execution completed'); }12.2 Throwing custom errors
class CustomError extends Error { constructor(message: string) { super(message); this.name = 'CustomError'; } } throw new CustomError('Invalid operation');12.3 Error types
try { null.f(); // TypeError } catch (e) { if (e instanceof TypeError) console.log('Type error occurred'); }12.4 Error inheritance
class ValidationError extends Error {} class DatabaseError extends Error {}12.5 Asynchronous error handling
async function fetchData() { try { const data = await fetch('invalid_url'); } catch (err) { console.log('Fetch failed:', err); } }12.6 Promise rejection handling
fetch('invalid_url') .then(res => res.json()) .catch(err => console.log('Error:', err));12.7 Async/await error handling
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
try { throw new CustomError('Oops'); } catch (err: unknown) { if (err instanceof CustomError) { console.log(err.message); } }12.9 Logging errors
try { throw new Error('Log me'); } catch (err) { console.error('Error logged:', err); }12.10 Custom error classes
class AuthError extends Error { constructor(public code: number, message: string) { super(message); this.name = 'AuthError'; } }12.11 Using enums for error codes
enum ErrorCode { NotFound = 404, Unauthorized = 401 } throw new AuthError(ErrorCode.Unauthorized, 'Access denied');12.12 Validation errors
function validate(name: string) { if(name.length < 3) throw new ValidationError('Name too short'); }12.13 HTTP errors
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
try { riskyOperation(); } catch (err) { handleError(err); }12.15 Debugging techniques
try { riskyOperation(); } catch (err) { console.log('Stack trace:', err.stack); }
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.get13.10 Type-safe third-party APIs:("https://api.example.com/user/1") .then(response => console.log(response.data.name));
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(): Promise13.11 Migrating JS projects to TypeScript:{ const response = await fetch("https://api.example.com/posts/1"); return response.json() as Promise ; }
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.
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); });
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));
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));
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));
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));
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();
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();
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();
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();
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);
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"), });
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));
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); } })();
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", ]);
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) );
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");
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
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!"); });
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}`); });
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"); });
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");
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);
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();
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";
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); });
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
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); });
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); });
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);
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!"));
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
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
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); });
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);
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'));
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'));
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);
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
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();
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
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');
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
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');
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); });
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; } }
// Simple test concept example function add(a: number, b: number): number { return a + b; } console.log(add(2, 3)); // Expected: 5
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); });
// 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); }); });
// Unit test example function multiply(a: number, b: number) { return a*b; } console.log(multiply(2, 3)); // 6
// 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"
// Jest mocking example jest.mock('./api', () => ({ fetchData: () => ({ id: 1, name: 'Mocked' }) }));
// Async test example async function fetchData() { return "data"; } fetchData().then(result => console.log(result)); // "data"
// Jest snapshot example import renderer from 'react-test-renderer'; const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot();
// Debugging: set a breakpoint on the following line const x = 10; console.log(x); // Inspect variable 'x' here
// Example: set breakpoint on the next line for(let i=0;i<5;i++){ console.log(i); // Execution stops here when debugging }
console.log("User data fetched:", { id: 1, name: "Alice" });
try { throw new Error("Something went wrong"); } catch(e) { console.error(e.stack); }
let num: number = 5; // num = "string"; // Error: Type 'string' is not assignable to type 'number'
// ESLint identifies potential issues, Prettier formats code automatically const foo = "bar"; // auto-formatted and lint-checked
// Jest coverage command // jest --coverage
npx create-react-app my-app --template typescript cd my-app npm start18.2 Functional Components
import React, { FC } from 'react'; interface Props { name: string; } const Greeting: FC<Props> = ({ name }) => { return <h1>Hello, {name}</h1>; };18.3 Props Typing
interface ButtonProps { label: string; onClick: () => void; } const Button: FC<ButtonProps> = ({ label, onClick }) => <button onClick={onClick}>{label}</button>;18.4 State Typing
const [count, setCount] = useState<number>(0);18.5 Event Handling
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { console.log(e.currentTarget); };18.6 Using useRef and useState
const inputRef = useRef<HTMLInputElement | null>(null); const [value, setValue] = useState<string>('');18.7 Context API with TypeScript
interface ThemeContextProps { darkMode: boolean; toggle: () => void; } const ThemeContext = React.createContext<ThemeContextProps | undefined>(undefined);18.8 React Hooks Typing
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
function withLogger<P>(Component: React.ComponentType<P>) { return (props: P) => { console.log('Props:', props); return <Component {...props} />; }; }18.10 Component Composition
const Card: FC<{title: string}> = ({ title, children }) => <div><h2>{title}</h2>{children}</div>;18.11 React Router Typing
import { useParams } from 'react-router-dom'; interface Params { id: string; } const { id } = useParams<Params>();18.12 Form Handling
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); console.log('Submitted'); };18.13 Third-Party Component Typing
import Select from 'react-select'; // @types/react-select for typings18.14 React Performance Patterns
const MemoComp = React.memo<FC<{value: number}>>(({ value }) => <div>{value}</div>);18.15 Best Practices in React TS
interface Props { message: string; } const Alert: FC<Props> = ({ message }) => <div>{message}</div>;
// Angular component example @Component({ selector: 'app-root', template: '<h1>Hello Angular</h1>' }) export class AppComponent {}19.2 TypeScript in Angular CLI
// Generate Angular project ng new my-app --strict cd my-app ng serve19.3 Components in Angular
@Component({ selector: 'app-user', template: '<div>User component</div>' }) export class UserComponent { name: string = 'Alice'; }19.4 Modules
@NgModule({ declarations: [AppComponent, UserComponent], imports: [BrowserModule], bootstrap: [AppComponent] }) export class AppModule {}19.5 Services and Dependency Injection
@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
<div *ngIf="isVisible">Visible content</div> <div *ngFor="let user of users">{{user}}</div>19.7 Pipes
<p>{{ today | date:'fullDate' }}</p> <p>{{ name | uppercase }}</p>19.8 Forms in Angular
// Reactive form example this.form = new FormGroup({ username: new FormControl('', Validators.required) });19.9 HTTP Client
this.http.get<User[]>('/api/users').subscribe(data => console.log(data));19.10 RxJS in Angular
this.users$ = this.http.get<User[]>('/api/users'); this.users$.subscribe(u => console.log(u));19.11 Angular Lifecycle Hooks
ngOnInit() { console.log('Component initialized'); } ngOnDestroy() { console.log('Cleanup'); }19.12 Routing and Navigation
const routes: Routes = [ { path: 'users', component: UserListComponent } ];19.13 Guards and Interceptors
@Injectable() export class AuthGuard implements CanActivate { canActivate() { return true; } }19.14 Testing Angular TS Code
describe('UserService', () => { it('should return users', () => { expect(service.getUsers()).toContain('Alice'); }); });19.15 Best Practices
// Follow consistent coding standards and type safety
// 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
# Terminal command vue create my-vue-app # Select TypeScript support20.3 Components in Vue
<script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ name: 'HelloWorld' }); </script>20.4 Props Typing
export default defineComponent({ props: { msg: String } });20.5 Reactive State
import { reactive } from 'vue'; const state = reactive({ count: 0 }); state.count++;20.6 Computed Properties
import { computed } from 'vue'; const doubleCount = computed(() => state.count * 2);20.7 Watchers
import { watch } from 'vue'; watch(() => state.count, (newVal, oldVal) => { console.log(`Count changed: ${oldVal} => ${newVal}`); });20.8 Vue Router with TypeScript
import { createRouter, createWebHistory } from 'vue-router'; const routes = [{ path: '/', component: Home }]; const router = createRouter({ history: createWebHistory(), routes });20.9 Vuex State Management
import { createStore } from 'vuex'; const store = createStore({ state: { count: 0 }, mutations: { increment(state) { state.count++ } } });20.10 Composition API
import { ref } from 'vue'; const count = ref<number>(0);20.11 Options API Typing
export default defineComponent({ data() { return { count: 0 }; }, methods: { increment() { this.count++ } } });20.12 Event Handling
<button @click="increment">Increment</button>20.13 Slots and Templates
<slot name="header" />20.14 Integrating Third-Party Libraries
import _ from 'lodash'; const arr: number[] = [1,2,3]; console.log(_.reverse(arr));20.15 Best Practices
// Keep components modular and type-safe
// 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 example const multiply = (a: number) => (b: number) => a * b; const double = multiply(2); console.log(double(5)); // 1021.3 Immutability Patterns
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
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
// Example: mixins type Constructor21.6 Decorator Patterns= 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();
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
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); // true21.8 Factory Pattern
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
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
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)); // 521.11 Mediator Pattern
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
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
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
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
// 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
$ tsc app.ts22.2 AST (Abstract Syntax Tree) overview
function add(a: number, b: number) { return a + b; } // AST: FunctionDeclaration -> Parameters -> ReturnStatement22.3 Type checking process
let num: number = 5; // num = 'hello'; // Error: Type 'string' is not assignable to type 'number'22.4 Transpilation vs compilation
$ tsc app.ts --target ES622.5 Incremental compilation
/* tsconfig.json */ { "compilerOptions": { "incremental": true } }22.6 Type declaration files
$ npm install --save-dev @types/lodash22.7 Module resolution
import _ from 'lodash'; import { add } from './utils';22.8 Source maps generation
$ tsc app.ts --sourceMap22.9 Compiler diagnostics
$ tsc app.ts --pretty22.10 Watch mode internals
$ tsc -w22.11 tsconfig parsing
/* tsconfig.json */ { "compilerOptions": { "target": "ES6", "module": "commonjs" } }22.12 Compiler APIs
import * as ts from 'typescript'; const program = ts.createProgram(['app.ts'], {});22.13 Language service
// Used in VSCode or IDEs to provide type info dynamically22.14 Performance optimization
/* tsconfig.json */ { "exclude": ["node_modules"] }22.15 Debugging compilation issues
$ tsc --traceResolution
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: Partial23.2 Required type:) => { console.log(user); }; updateUser({ name: "Majid" }); // Only 'name' provided
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: Required23.3 Readonly type:= { name: "Majid", age: 30 }; // Error if any property missing
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: Readonly23.4 Record type:= { name: "Majid", age: 30 }; // user.age = 31; // Error: Cannot assign to 'age'
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: Record23.5 Pick type:= { math: 90, science: 85 };
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 = Pick23.6 Omit type:; const preview: UserPreview = { name: "Majid", email: "majid@example.com" };
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 = Omit23.7 Exclude type:; const user2: UserWithoutEmail = { name: "Majid", age: 30 };
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 = Exclude23.8 Extract type:; // string | number
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 = Extract23.9 NonNullable type:; // number | boolean
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 = NonNullable23.10 ReturnType utility:; // string only
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 = ReturnType23.11 Parameters utility:; const user3: UserType = { name: "Majid", age: 30 };
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 = Parameters23.12 InstanceType utility:; // [string, number]
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 = InstanceType23.13 Conditional types:; const userObj: UserInstance = new UserClass();
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 IsString23.14 Mapped types:= T extends string ? "Yes" : "No"; type Test1 = IsString ; // Yes type Test2 = IsString ; // No
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; // Error23.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
const btn = document.getElementById('myBtn') as HTMLButtonElement; btn.disabled = true;24.2 Fetch API with TS
fetch('https://api.example.com/data') .then((res) => res.json() as Promise<DataType>) .then(data => console.log(data));24.3 WebSockets typing
const ws = new WebSocket('ws://localhost'); ws.onmessage = (event: MessageEvent<string>) => console.log(event.data);24.4 LocalStorage & SessionStorage
localStorage.setItem('key', JSON.stringify({name: 'Ali'})); const data = JSON.parse(localStorage.getItem('key')!) as {name: string};24.5 IndexedDB
const request = indexedDB.open('MyDB', 1); request.onsuccess = (event) => { const db = (event.target as IDBOpenDBRequest).result; };24.6 Canvas API typing
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
navigator.geolocation.getCurrentPosition((position: GeolocationPosition) => { console.log(position.coords.latitude, position.coords.longitude); });24.8 Web Workers typing
const worker = new Worker('worker.js'); worker.onmessage = (e: MessageEvent<string>) => console.log(e.data); worker.postMessage('Hello Worker');24.9 EventSource API
const evtSource = new EventSource('/events'); evtSource.onmessage = (e: MessageEvent<string>) => console.log(e.data);24.10 Service Workers
navigator.serviceWorker.register('/sw.js') .then(reg => console.log('Service Worker registered'));24.11 Notifications API
new Notification('Hello', {body: 'You have a message'});24.12 Media APIs (audio/video)
const video = document.querySelector('video') as HTMLVideoElement; navigator.mediaDevices.getUserMedia({video: true}).then(stream => video.srcObject = stream);24.13 Drag & drop
const el = document.getElementById('drag') as HTMLElement; el.ondragstart = (e: DragEvent) => console.log(e.dataTransfer);24.14 File API
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
// Example best practice if('geolocation' in navigator) { navigator.geolocation.getCurrentPosition(pos => console.log(pos.coords)); }
const jsonString = '{"name": "Ali", "age": 25}'; const obj: { name: string; age: number } = JSON.parse(jsonString);25.2 JSON typing
interface User { name: string; age: number; } const user: User = JSON.parse('{"name": "Ali", "age": 25}');25.3 Serializing objects
const user = { name: 'Ali', age: 25 }; const jsonStr: string = JSON.stringify(user);25.4 Type-safe API responses
interface ApiResponse { id: number; name: string; } const res = await fetch('/api/user'); const data: ApiResponse = await res.json();25.5 Interfaces for JSON
interface Todo { id: number; title: string; completed: boolean; } const todo: Todo = JSON.parse('{"id":1,"title":"Task","completed":false}');25.6 Nested JSON typing
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
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
const userData = JSON.parse('{"name":"Ali"}'); const age: number = userData.age ?? 18;25.9 JSON schema validation
// 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
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
const obj: any = JSON.parse('{"name":"Ali"}'); if(typeof obj.name !== 'string') throw new Error('Invalid data');25.12 Using io-ts library
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
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
try { const user = JSON.parse('{"name":"Ali"}'); } catch(e) { console.error('Invalid JSON', e); }25.15 Best practices
interface User { name: string; age: number; } const user: User = JSON.parse('{"name":"Ali","age":25}');
$ tsc --incremental26.2 Tree shaking
import { usedFunction } from './utils'; // Unused functions are removed during bundling26.3 Avoiding unnecessary any types
let name: string = 'Ali'; // instead of let name: any26.4 Module bundlers (Webpack, Rollup)
// webpack.config.js example export default { entry: './src/index.ts', output: { filename: 'bundle.js' }, };26.5 Lazy loading
const module = await import('./featureModule');26.6 Code splitting
// webpack example optimization: { splitChunks: { chunks: 'all' } }26.7 Minification & compression
// Using Terser plugin in Webpack const TerserPlugin = require('terser-webpack-plugin');26.8 Memory optimization
let arr: number[] = new Array(1000); // allocate efficiently26.9 Using const & readonly
const PI = 3.14; class Circle { readonly radius: number; constructor(r: number) { this.radius = r; } }26.10 Avoiding type assertions
let val: unknown = 'test'; let str: string = val as string; // use only when necessary26.11 Profiling TypeScript apps
// Node.js profiling example node --inspect-brk app.js26.12 Compiler options for speed
/* tsconfig.json */ { "compilerOptions": { "incremental": true, "skipLibCheck": true } }26.13 Incremental builds
$ tsc --incremental26.14 Reducing type complexity
type Simple = string | number;26.15 Best practices
// Follow clean coding and optimized typing principles
// Example GraphQL query const query = ` query GetUser($id: ID!) { user(id: $id) { id name } } `;27.2 Setting up Apollo Client
import { ApolloClient, InMemoryCache } from '@apollo/client'; const client: ApolloClient<any> = new ApolloClient({ uri: '/graphql', cache: new InMemoryCache(), });27.3 Typed queries
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
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
// Example type generated from schema interface Query { user(id: string): User; }27.6 Scalars & enums
type Role = 'ADMIN' | 'USER'; interface User { role: Role; }27.7 Input types
interface UpdateUserInput { id: string; name?: string; }27.8 Interfaces & unions
interface Admin { role: 'ADMIN'; permissions: string[]; } interface User { role: 'USER'; } type Account = Admin | User;27.9 Fragments in TypeScript
const userFragment = ` fragment UserFields on User { id name } `;27.10 Code generation (GraphQL Code Generator)
// Install and run codegen $ graphql-codegen --config codegen.yml27.11 Query variables typing
interface GetUserVars { id: string; }27.12 Response typing
interface GetUserResponse { user: User; }27.13 Error handling
try { const { data } = await client.query<GetUserResponse, GetUserVars>({ query: GET_USER, variables: { id: '1' } }); } catch (error) { console.error(error); }27.14 Subscriptions typing
const subscription = client.subscribe<{ message: string }>({ query: MESSAGE_SUBSCRIPTION }); subscription.subscribe({ next({ data }) { console.log(data.message); } });27.15 Best practices
// Example: use generated types for safe queries const { data } = await client.query<GetUserQuery, GetUserQueryVariables>({ query: GET_USER, variables: { id: '1' } });
let username: string = 'admin'; // username = 123; // Error: Type 'number' is not assignable to type 'string'28.2 Input validation
function isValidName(name: string): boolean { return /^[a-zA-Z]+$/.test(name); }28.3 XSS prevention
function escapeHTML(str: string) { return str.replace(/&/g, '&') .replace(//g, '>'); }28.4 CSRF protection
// Example: Adding CSRF token to request header fetch('/api/data', { headers: { 'X-CSRF-Token': token } });28.5 Avoiding eval and unsafe code
// Avoid // eval('alert(1)');28.6 Safe JSON parsing
try { const obj = JSON.parse(userInput); } catch (err) { console.error('Invalid JSON'); }28.7 Strict null checks
let name: string | null = null; // name.toLowerCase(); // Error if strictNullChecks is true28.8 HTTPS enforcement
// Example: redirect HTTP to HTTPS in server config28.9 Secure cookies
document.cookie = 'session=abc; Secure; HttpOnly';28.10 Secure localStorage usage
// Example: encrypt data before storing localStorage.setItem('token', btoa(token));28.11 Dependency audit
$ npm audit28.12 Secrets management
const apiKey = process.env.API_KEY;28.13 Avoiding prototype pollution
// Safe approach const obj = Object.create(null); obj.user = 'Ali';28.14 Webpack security tips
// webpack.config.js example module.exports = { mode: 'production', devtool: 'source-map' };28.15 Best security practices
// Example: Combined security practices function safeParse(str: string) { try { return JSON.parse(str); } catch { return null; } }
// Example: Message interface interface Messages { greeting: string; } const en: Messages = { greeting: 'Hello' };29.2 Using i18next with TS
import i18next from 'i18next'; i18next.init({ lng: 'en', resources: { en: { translation: { greeting: 'Hello' } } } });29.3 Message typing
interface Translation { greeting: string; } const messages: Translation = { greeting: 'Hello' };29.4 Language switching
i18next.changeLanguage('fr');29.5 Pluralization
i18next.t('item', { count: 2 }); // returns '2 items'29.6 Date & time localization
const date = new Date(); const formatted = new Intl.DateTimeFormat('fr-FR').format(date);29.7 Number formatting
const num = 123456.78; const formatted = new Intl.NumberFormat('de-DE').format(num);29.8 Currency formatting
const amount = 1000; const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);29.9 Translation loading
i18next.loadLanguages(['fr', 'de']);29.10 Context-based translations
i18next.t('button.save', { context: 'admin' });29.11 Dynamic messages
i18next.t('greeting_user', { name: 'Ali' });29.12 Integration with React
import { useTranslation } from 'react-i18next'; const { t } = useTranslation(); <p>{t('greeting')}</p>29.13 Integration with Angular
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
i18next.on('missingKey', (lng, ns, key) => console.warn(`Missing ${key} in ${lng}`));29.15 Best practices
// Use interfaces and type-safe loading interface Messages { greeting: string; }
// 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
// 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
// Example: Mocking a module jest.mock('./api'); import { fetchData } from './api'; fetchData.mockResolvedValue({ data: 'mock' });30.4 Spy functions
const spy = jest.spyOn(console, 'log'); console.log('Hello'); expect(spy).toHaveBeenCalledWith('Hello');30.5 Asynchronous testing
test('async test', async () => { const result = await fetchData(); expect(result.data).toBeDefined(); });30.6 Snapshot testing
test('component snapshot', () => { const tree = renderer.create(<MyComponent />).toJSON(); expect(tree).toMatchSnapshot(); });30.7 Testing React components
import { render, screen } from '@testing-library/react'; import App from './App'; render(<App />); expect(screen.getByText('Hello')).toBeInTheDocument();30.8 Testing Angular components
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
// Example: Cypress test cy.visit('/'); cy.contains('Login').click(); cy.url().should('include', '/dashboard');30.10 Cypress overview
// Visit page and interact element = cy.get('#submit'); element.click();30.11 Test coverage reporting
// Jest coverage jest --coverage30.12 Continuous testing
// GitHub Actions example jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: npm install - run: npm test30.13 Test-driven development (TDD)
test('adds numbers', () => { expect(sum(2, 3)).toBe(5); }); // Then implement sum function30.14 Behavior-driven development (BDD)
describe('Calculator', () => { it('adds numbers correctly', () => { expect(sum(1,2)).toBe(3); }); });30.15 Best testing practices
// Example: simple Jest test structure describe('MyModule', () => { test('function behaves correctly', () => { expect(func()).toBeTruthy(); }); });
// Example: Simple state object interface AppState { count: number; } const state: AppState = { count: 0 };31.2 Redux with TypeScript
import { createStore } from 'redux'; interface State { count: number; } const initialState: State = { count: 0 };31.3 Actions and action types
interface IncrementAction { type: 'INCREMENT'; } interface DecrementAction { type: 'DECREMENT'; } type Action = IncrementAction | DecrementAction;31.4 Reducers typing
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
const store = createStore(counterReducer, initialState); store.dispatch({ type: 'INCREMENT' });31.6 Thunks
import { ThunkAction } from 'redux-thunk'; const incrementAsync = (): ThunkAction<void, State, unknown, Action> => dispatch => { setTimeout(() => dispatch({ type: 'INCREMENT' }), 1000); };31.7 Middleware typing
const logger = store => next => action => { console.log('Dispatching', action); return next(action); };31.8 React Context API
import React, { createContext, useContext } from 'react'; interface AppContext { count: number; } const AppContext = createContext<AppContext>({ count: 0 });31.9 MobX typing
import { observable } from 'mobx'; class Store { @observable count = 0; }31.10 Zustand typing
import create from 'zustand'; const useStore = create<{ count: number; increment: () => void }>(() => ({ count: 0, increment: () => {} }));31.11 NgRx overview
import { createReducer, on } from '@ngrx/store';31.12 Vuex with TypeScript
import { createStore } from 'vuex'; interface State { count: number; } const store = createStore<State>({ state: { count: 0 } });31.13 State immutability
const newState = { ...state, count: state.count + 1 };31.14 Async state handling
async function fetchAndUpdate() { const data = await fetch('/api').then(res => res.json()); }31.15 Best practices
// Example: Always type actions and state interface State { count: number; } interface Action { type: string; payload?: any; }
// webpack.config.js module.exports = { entry: './src/index.ts', output: { filename: 'bundle.js' } };32.2 ts-loader setup
// webpack.config.js module: { rules: [ { test: /\.ts$/, use: 'ts-loader' } ] }32.3 Babel integration
// webpack.config.js module: { rules: [ { test: /\.ts$/, use: ['babel-loader', 'ts-loader'] } ] }32.4 Entry and output configuration
entry: './src/index.ts', output: { path: __dirname + '/dist', filename: 'bundle.js' }32.5 Loaders and plugins
module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ]32.6 Source maps
devtool: 'source-map'32.7 Hot module replacement
plugins: [ new webpack.HotModuleReplacementPlugin() ] devServer: { hot: true }32.8 Code splitting
output: { filename: '[name].bundle.js' }, optimization: { splitChunks: { chunks: 'all' } }32.9 Tree shaking
mode: 'production'32.10 Production vs development builds
mode: 'development' // or 'production'32.11 Environment variables
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') })32.12 Performance optimization
optimization: { minimize: true }32.13 Module resolution
resolve: { extensions: ['.ts', '.js'], alias: { '@': path.resolve(__dirname, 'src') } }32.14 Multiple entry points
entry: { main: './src/index.ts', admin: './src/admin.ts' }32.15 Best bundling practices
// Always analyze bundle size const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; plugins: [ new BundleAnalyzerPlugin() ]
# Check Docker version $ docker --version33.2 Dockerfile for TypeScript apps
# 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
# 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
EXPOSE 3000 WORKDIR /app COPY . . CMD ["node", "dist/index.js"]33.5 Mounting volumes
docker run -v $(pwd):/app -p 3000:3000 my-app33.6 Docker Compose
# docker-compose.yml services: app: build: . ports: - "3000:3000" - ".:/app" command: npm start33.7 Environment variables
ENV NODE_ENV=production # or docker-compose environment section33.8 Container networking
docker network create my-network docker run --network my-network ...33.9 Health checks
HEALTHCHECK CMD curl --fail http://localhost:3000 || exit 133.10 Logging
docker logs -f my-container33.11 Building images
docker build -t my-app:latest .33.12 Pushing to Docker Hub
docker push myusername/my-app:latest33.13 Kubernetes overview
kubectl get pods kubectl apply -f deployment.yaml33.14 Deployment 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
# Keep images small and secure # Separate build and runtime # Use CI/CD for deployment
// Example GET request fetch('/api/users').then(res => res.json()).then(data => console.log(data));34.2 Creating API with Express.js
import express from 'express'; const app = express(); app.get('/users', (req, res) => { res.json([{name: 'Ali'}]); }); app.listen(3000);34.3 Routing typing
import { Request, Response } from 'express'; app.get('/users', (req: Request, res: Response) => { res.send('OK'); });34.4 Request and response types
interface User { name: string; age: number; } app.post('/users', (req: Request<{}, {}, User>, res: Response) => { res.send(req.body); });34.5 Middleware typing
import { NextFunction } from 'express'; app.use((req: Request, res: Response, next: NextFunction) => { console.log(req.url); next(); });34.6 Error handling
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
interface Query { search: string; } app.get('/search', (req: Request<{}, {}, {}, Query>, res: Response) => { res.send(req.query.search); });34.8 Path parameters typing
interface Params { id: string; } app.get('/users/:id', (req: Request<Params>, res: Response) => { res.send(req.params.id); });34.9 Body parsing typing
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
app.get('/users', async (req: Request, res: Response) => { const users = await getUsers(); res.send(users); });34.11 Response validation
const users: User[] = await getUsers(); res.json(users);34.12 API documentation typing
/** * @swagger * /users: * get: * responses: * 200: * description: List of users */34.13 Using OpenAPI
import swaggerJsdoc from 'swagger-jsdoc'; const specs = swaggerJsdoc({ definition, apis: ['./routes/*.ts'] });34.14 Authentication & authorization
interface User { id: string; name: string; } app.use((req: Request, res: Response, next: NextFunction) => { /* auth logic */ next(); });34.15 Best practices
// Example: Typed Express route interface Params { id: string; } app.get('/users/:id', (req: Request<Params>, res: Response) => { res.send(req.params.id); });
// Microservice folder structure example services/ user-service/ order-service/35.2 Service typing
interface User { id: string; name: string; } interface CreateUserRequest { name: string; }35.3 Inter-service communication
// Example HTTP call between services fetch('http://order-service/orders').then(res => res.json());35.4 gRPC with TypeScript
// Generated gRPC TypeScript code import { UserServiceClient } from './proto/user_grpc_pb';35.5 REST microservices
app.get('/users', (req: Request, res: Response) => { res.json(users); });35.6 Message brokers
interface UserCreatedEvent { id: string; name: string; }35.7 Event-driven architecture
// Event listener example on('user.created', (event: UserCreatedEvent) => console.log(event.name));35.8 Data consistency
// Using types for consistency interface Order { id: string; userId: string; total: number; }35.9 Logging & monitoring
import { logger } from './logger'; logger.info('User service started');35.10 Circuit breaker pattern
// Pseudocode if(failureCount > 5) openCircuit();35.11 API gateways
// Example: Gateway forwarding request forwardRequest<User>('/users');35.12 Service discovery
const userServiceUrl: string = discoverService('user-service');35.13 Testing microservices
test('create user', () => { expect(createUser('Ali').name).toBe('Ali'); });35.14 Scaling microservices
// Horizontal scaling configuration example replicas: 335.15 Best practices
interface PaymentServiceRequest { amount: number; userId: string; }
// Pure function example const add = (a: number, b: number): number => a + b;36.2 Pure functions
const multiply = (x: number, y: number): number => x * y;36.3 Immutability
const arr = [1,2,3]; const newArr = [...arr, 4]; // original arr unchanged36.4 Higher-order functions
const map = (f: (x: number) => number) => (arr: number[]) => arr.map(f);36.5 Currying
const addC = (a: number) => (b: number) => a + b; const sum5 = addC(5); sum5(3); // 836.6 Partial application
const multiplyC = (a: number, b: number) => a * b; const double = multiplyC.bind(null, 2); double(5); // 1036.7 Function composition
const compose = (f: Function, g: Function) => (x: any) => f(g(x));36.8 Monads overview
// Using Maybe from fp-ts import { some, none } from 'fp-ts/Option';36.9 Option/Maybe types
const value = some(5); const noValue = none;36.10 Either type
import { left, right } from 'fp-ts/Either'; const result = right(42);36.11 Functor concept
value.map(x => x * 2); // Option functor mapping36.12 FP libraries (Ramda, fp-ts)
import * as R from 'ramda'; R.map(x => x * 2, [1,2,3]); // [2,4,6]36.13 Declarative coding patterns
const evens = [1,2,3,4].filter(x => x % 2 === 0);36.14 Error handling with FP
import { tryCatch } from 'fp-ts/Either'; const result = tryCatch(() => JSON.parse('{"a":1}'), e => e);36.15 Best practices
const increment = (x: number) => x + 1;
<!-- Example: Simple CLI app --> console.log('Welcome to My CLI App!');37.2 Using yargs
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
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
const args = process.argv.slice(2); const firstArg = args[0]; console.log('First argument:', firstArg);37.5 Input validation
const age = Number(program.opts().age); if(isNaN(age)) { console.error('Age must be a number'); process.exit(1); }37.6 Colored console output
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
function logInfo(message: string) { console.log('[INFO]', message); } logInfo('CLI started');37.8 Async CLI tasks
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
import fs from 'fs'; fs.writeFileSync('output.txt', 'CLI output using TypeScript');37.10 Interactive prompts
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
program.command('start').alias('s').action(() => console.log('Started'));37.12 Subcommands
program.command('build <project>').description('Build project').action((proj) => console.log('Building', proj));37.13 Packaging CLI apps
/* package.json */ { "name": "my-cli", "version": "1.0.0", "bin": { "mycli": "./dist/index.js" } }37.14 Publishing to npm
tsc npm publish37.15 Best practices
console.log('Always validate input, handle errors, and log messages consistently.');
const ws = new WebSocket('ws://localhost:8080'); ws.onmessage = (event: MessageEvent) => console.log('Message:', event.data);38.2 Socket.IO setup
import { Server } from 'socket.io'; const io = new Server(3000); io.on('connection', socket => { console.log('Client connected:', socket.id); });38.3 Typing events
interface ChatMessage { user: string; message: string; } socket.on('chat', (msg: ChatMessage) => console.log(msg.user, msg.message));38.4 Broadcasting messages
socket.broadcast.emit('chat', { user: 'Alice', message: 'Hello everyone!' });38.5 Rooms and namespaces
socket.join('room1'); io.to('room1').emit('notification', 'Welcome to room 1!');38.6 Client-server communication
interface Update { id: number; status: string; } socket.emit('update', { id: 1, status: 'done' });38.7 Error handling
socket.on('error', (err: Error) => console.error('Socket error:', err.message));38.8 Reconnection logic
socket.io.on('reconnect_attempt', (attempt) => console.log('Reconnect attempt', attempt));38.9 Real-time notifications
interface Notification { title: string; body: string; } socket.emit('notify', { title: 'Alert', body: 'New message!' });38.10 Chat applications
socket.on('chat', (msg: ChatMessage) => console.log(msg.user, msg.message));38.11 Collaborative tools
interface CanvasUpdate { x: number; y: number; color: string; } socket.emit('draw', { x: 10, y: 20, color: 'red' });38.12 Performance considerations
socket.emit('update', { id: 1, value: 'optimized' }); // minimal payload38.13 Testing real-time apps
import { io } from 'socket.io-client'; const testSocket = io('http://localhost:3000'); testSocket.emit('chat', { user: 'Test', message: 'Hello' });38.14 Security considerations
socket.on('update', (data: Update) => { if(!data.id) throw new Error('Invalid payload'); });38.15 Best practices
interface EventPayload { event: string; data: any; } socket.emit('event', { event: 'ping', data: {} });
<!-- 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
<!-- 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
<!-- 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
<!-- 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
<!-- 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
<!-- 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
<!-- 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
<!-- 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
<!-- Example: Firebase Auth login --> import { getAuth, signInWithEmailAndPassword } from 'firebase/auth'; const auth = getAuth(); await signInWithEmailAndPassword(auth, 'user@example.com', 'password');39.10 Serverless functions
<!-- 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
<!-- Example: CI/CD pipeline step in TypeScript --> const deploy = async () => { console.log('Deploying app...'); }; await deploy();39.12 Monitoring & logging
<!-- Example: Logging in Cloud Functions --> import { logger } from 'firebase-functions'; logger.info('Function executed successfully');39.13 Secrets management
<!-- Example: Using environment variables --> const apiKey: string = process.env.API_KEY!;39.14 Event triggers
<!-- 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
<!-- Example: Type-safe Cloud SDK usage --> interface User { id: string; name: string; } const users: User[] = [{ id: '1', name: 'Alice' }];
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
type ElementType<T> = T extends (infer U)[] ? U : T; type Item = ElementType<string[]>; // string40.3 Recursive types
type NestedArray<T> = T | NestedArray<T>[]; const nums: NestedArray<number> = [1, [2, [3]]];40.4 Template literal types
type EventName = `on${string}Event`; const clickEvent: EventName = 'onClickEvent';40.5 Module augmentation
import 'express'; declare module 'express' { interface Request { user?: string; } }40.6 Declaration merging
interface User { name: string; } interface User { age: number; } const u: User = { name: 'Alice', age: 30 };40.7 Type inference optimization
const numbers = [1, 2, 3]; // inferred as number[] const doubled = numbers.map(n => n * 2);40.8 Performance tuning
/* tsconfig.json */ { "compilerOptions": { "incremental": true, "skipLibCheck": true } }40.9 Custom compiler APIs
import * as ts from 'typescript'; const sourceFile = ts.createSourceFile('app.ts', 'const x: number = 5;', ts.ScriptTarget.Latest);40.10 Large-scale project structuring
src/ components/ services/ utils/40.11 Monorepos with TypeScript
packages/ core/ web-app/ shared-types/40.12 CI/CD for TS projects
# Example: GitHub Actions step - name: Compile TypeScript run: tsc --noEmit40.13 Advanced debugging patterns
const data: string | null = null; console.log(data?.toUpperCase()); // safely handled40.14 Refactoring techniques
function add(a: number, b: number) { return a + b; } const sum = add(2, 3);40.15 Career guidance and best practices
interface Developer { name: string; expertise: string[]; } const dev: Developer = { name: 'Alice', expertise: ['TypeScript', 'GraphQL'] };
// WASM runs in the browser alongside JS // Example: Load a simple WASM module41.2 TypeScript and WASM overview
// AssemblyScript: TS syntax to WASM export function add(a: i32, b: i32): i32 { return a + b; }41.3 Setting up AssemblyScript
npm install --save-dev assemblyscript npx asinit .41.4 Compiling TypeScript to WASM
npx asc assembly/index.ts -b build/module.wasm -t build/module.wat41.5 Exporting functions to JS
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
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
let memory = new WebAssembly.Memory({ initial: 1 }); let view = new Uint8Array(memory.buffer);41.8 Passing complex data structures
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
npx asc assembly/index.ts -O3 -b build/module.wasm41.10 Debugging WASM modules
// Generate source maps npx asc assembly/index.ts -g -b build/module.wasm41.11 Using third-party WASM libraries
import * as wasmCrypto from 'wasm-crypto'; // wasmCrypto.hash(...)41.12 WASM and web security
// WASM module runs in isolated memory and execution context41.13 Real-time applications with WASM
// WASM can process frames, audio, or physics calculations in real-time41.14 Benchmarking TypeScript + WASM
console.time('wasm'); const result = wasmModule.exports.calculate(); console.timeEnd('wasm');41.15 Best practices
// Keep WASM modules small and modular // Use typed interfaces for interop
// TypeScript ensures type safety for ML pipelines import * as tf from '@tensorflow/tfjs';42.2 TensorFlow.js overview
import * as tf from '@tensorflow/tfjs'; const model = tf.sequential();42.3 Creating neural networks
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
await model.compile({ optimizer: 'sgd', loss: 'meanSquaredError' }); await model.fit(trainingData, trainingLabels, { epochs: 50 });42.5 Data preprocessing
const inputTensor = tf.tensor2d(data, [data.length, featuresCount]); const normalized = inputTensor.div(tf.scalar(255));42.6 Using pre-trained models
const mobilenet = await tf.loadLayersModel('https://.../mobilenet/model.json');42.7 Image classification projects
const prediction = mobilenet.predict(imageTensor) as tf.Tensor;42.8 NLP projects
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
model.add(tf.layers.dense({ units: 1, activation: 'linear' }));42.10 Model evaluation
const evalResult = model.evaluate(testData, testLabels) as tf.Scalar[];42.11 Model deployment
await model.save('localstorage://my-model');42.12 Performance optimization
tf.tidy(() => { const result = model.predict(inputTensor) as tf.Tensor; });42.13 Integrating with Node.js backend
import * as tf from '@tensorflow/tfjs-node'; const model = await tf.loadLayersModel('file://model/model.json');42.14 Visualization of predictions
chart.data = predictions.map(p => p.dataSync()[0]); chart.update();42.15 Best practices
// Define tensor shapes and types // Modularize ML pipelines // Use pre-trained models where possible
// Define a basic custom element class MyComponent extends HTMLElement { constructor() { super(); console.log('Component created'); } } customElements.define('my-component', MyComponent);43.2 Custom elements
class GreetingElement extends HTMLElement { connectedCallback() { this.innerHTML = '<p>Hello, TypeScript!</p>'; } } customElements.define('greeting-element', GreetingElement);43.3 Shadow DOM
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
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
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
const style = document.createElement('style'); style.textContent = 'p { color: blue; }'; shadow.appendChild(style);43.7 Event handling
this.dispatchEvent(new CustomEvent('greet', { detail: 'Hello' })); this.addEventListener('greet', (e: CustomEvent) => console.log(e.detail));43.8 Lifecycle callbacks
class LifeCycleComponent extends HTMLElement { connectedCallback() { console.log('Attached'); } disconnectedCallback() { console.log('Removed'); } } customElements.define('lifecycle-component', LifeCycleComponent);43.9 Attributes and properties
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
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
const event = new CustomEvent<'update'>('update', { detail: 42 }); component.dispatchEvent(event);43.12 Integrating with frameworks
// Using a web component in React <my-component count={10} />43.13 Using libraries with TS
import { LitElement, html } from 'lit'; class MyLit extends LitElement { render() { return html`<p>Lit component</p>`; } }43.14 Testing web components
const component = document.createElement('my-component') as HTMLElement; component.setAttribute('count', '5');43.15 Best practices
// Define types for props and events // Encapsulate styles // Use templates for reusable content
// Simple TypeScript function to check PWA support function isPWASupported(): boolean { return 'serviceWorker' in navigator; } console.log(isPWASupported());44.2 Service workers setup
navigator.serviceWorker.register('/sw.js') .then((registration) => console.log('SW registered', registration)) .catch((error) => console.error('SW registration failed', error));44.3 Caching strategies
self.addEventListener('fetch', (event: FetchEvent) => { event.respondWith(caches.match(event.request) || fetch(event.request)); });44.4 Offline support
if (!navigator.onLine) { console.log('You are offline'); }44.5 Web App Manifest
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
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
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
const shell = document.getElementById('app-shell') as HTMLElement; shell.innerHTML = '<header>Header</header><main></main>';44.9 IndexedDB usage
const request = indexedDB.open('pwa-db', 1); request.onsuccess = (event) => console.log('DB opened', event);44.10 Updating service workers
navigator.serviceWorker.getRegistrations() .then((regs) => regs.forEach((r) => r.update()));44.11 Performance optimization
console.time('load'); // load app shell and resources console.timeEnd('load');44.12 Security considerations
if (location.protocol !== 'https:') console.warn('PWA should run over HTTPS');44.13 Lighthouse auditing
const config = { extends: 'lighthouse:default' };44.14 Deploying PWAs
import * as fs from 'fs'; fs.copyFileSync('manifest.json', 'dist/manifest.json');44.15 Best practices
// Type interfaces for SW events // Typed IndexedDB interactions // Typed caching and notifications
async function fetchData(url: string): Promise<any> { const response = await fetch(url); return await response.json(); }45.2 WebSockets advanced patterns
const ws = new WebSocket('wss://example.com'); ws.onmessage = (event: MessageEvent<string>) => console.log(event.data);45.3 SSE (Server-Sent Events)
const evtSource = new EventSource('/events'); evtSource.onmessage = (event: MessageEvent<string>) => console.log(event.data);45.4 GraphQL subscriptions
import { gql } from '@apollo/client'; const SUBSCRIBE_MESSAGES = gql`subscription { messageAdded { id text } }`;45.5 WebRTC introduction
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
pc.onicecandidate = (event: RTCPeerConnectionIceEvent) => console.log(event.candidate);45.7 Media streams typing
const audioTrack: MediaStreamTrack = stream.getAudioTracks()[0]; audioTrack.enabled = true;45.8 Data channel management
const dc = pc.createDataChannel('chat'); dc.onmessage = (event: MessageEvent<string>) => console.log(event.data);45.9 Secure communication
const secureUrl: string = 'wss://secure.example.com'; const ws = new WebSocket(secureUrl);45.10 Retry and backoff strategies
function retry(fn: () => Promise<any>, retries: number) { /* ... */ }45.11 Network error handling
try { const data = await fetchData('/api'); } catch (error: unknown) { console.error(error); }45.12 Fetch API advanced usage
const controller = new AbortController(); fetch('/api', { signal: controller.signal }); controller.abort();45.13 Custom headers and authentication
const headers: HeadersInit = { Authorization: 'Bearer TOKEN' }; fetch('/api', { headers });45.14 CORS management
fetch('https://api.example.com', { mode: 'cors' });45.15 Best practices
// Typed fetch requests // WebSocket event typing // Retry/backoff strategies
interface Transaction { from: string; to: string; amount: number; timestamp: Date; }46.2 Smart contracts overview
interface Contract { address: string; abi: any[]; }46.3 TypeScript with Ethereum
const ethAddress: string = '0x123...abc';46.4 Using web3.js
import Web3 from 'web3'; const web3: Web3 = new Web3('https://mainnet.infura.io/v3/YOUR_KEY');46.5 Connecting to networks
web3.setProvider(new Web3.providers.HttpProvider('https://ropsten.infura.io'));46.6 Reading blockchain data
const blockNumber: number = await web3.eth.getBlockNumber();46.7 Writing transactions
const txHash: string = await web3.eth.sendTransaction({ from: '0xabc...', to: '0xdef...', value: web3.utils.toWei('1', 'ether') });46.8 Event listeners
contract.events.Transfer((error, event) => console.log(event.returnValues));46.9 Type-safe contract interaction
interface MyToken { transfer(to: string, amount: number): Promise<boolean>; }46.10 Unit testing contracts
await myToken.transfer('0xdef...', 100);46.11 Handling gas fees
const gasEstimate: number = await myToken.methods.transfer('0xdef...', 100).estimateGas();46.12 Security considerations
// Type-safe address validation const isValid: boolean = web3.utils.isAddress('0xabc...');46.13 NFT development
interface NFT { tokenId: number; owner: string; metadataURI: string; }46.14 DeFi integrations
await lendingPool.deposit('0xabc...', 10);46.15 Best practices
// Typed smart contract methods // Typed events // Gas fee management
interface MicroFrontend { name: string; url: string; mount: () => void; }47.2 Architecture overview
const mfeRegistry: MicroFrontend[] = [ { name: 'app1', url: '/app1.js', mount: () => console.log('Mounted app1') }, ];47.3 Module federation
import { Header } from 'app1/Header';47.4 Isolated build processes
// Each app has its own tsconfig.json and build script47.5 Communication between apps
const eventPayload: { userId: number; action: string } = { userId: 1, action: 'login' }; window.dispatchEvent(new CustomEvent('userAction', { detail: eventPayload }));47.6 Shared state management
interface GlobalState { user: string; token: string; } const state: GlobalState = { user: 'Alice', token: 'abc123' };47.7 Routing across micro frontends
interface Route { path: string; component: () => void; }47.8 Lazy loading micro frontends
const loadApp = async () => { const module = await import('app1/Header'); module.mount(); }47.9 Deployment strategies
// CI/CD pipeline deploys each app separately47.10 Testing micro frontends
test('App1 header mounts', () => { expect(Header.mount).not.toThrow(); });47.11 Versioning considerations
interface App1V2 { newProp: string; }47.12 Performance monitoring
const metric: { loadTime: number; app: string } = { loadTime: 120, app: 'app1' };47.13 Security practices
// Validate messages before processing47.14 Using TypeScript in multiple apps
export interface SharedUser { id: number; name: string; }47.15 Best practices
// Type-safe communication // Independent deployments // Shared type definitions
interface Sensor { id: string; value: number; timestamp: Date; }48.2 Node.js with IoT devices
import { readSensor } from 'iot-lib'; const temperature: number = readSensor('tempSensor');48.3 TypeScript SDKs for devices
import { LED } from 'iot-device-sdk'; const led: LED = new LED('led1');48.4 Reading sensor data
const sensorData: Sensor = { id: 's1', value: 42, timestamp: new Date() };48.5 Controlling actuators
interface ActuatorCommand { id: string; action: string; } const command: ActuatorCommand = { id: 'motor1', action: 'start' };48.6 Communication protocols (MQTT, HTTP)
const mqttPayload: { topic: string; message: string } = { topic: 'sensor/1', message: '42' };48.7 Real-time data streaming
import { Observable } from 'rxjs'; const dataStream: Observable<Sensor> = getSensorStream();48.8 Data visualization
interface ChartData { timestamp: Date; value: number; } const chartData: ChartData[] = [ { timestamp: new Date(), value: 42 } ];48.9 Edge computing concepts
function preprocessData(data: Sensor[]): Sensor[] { return data.map(d => ({ ...d, value: d.value * 1.1 })); }48.10 Security in IoT
interface AuthToken { token: string; expires: Date; }48.11 Error handling and logging
try { readSensor('temp1'); } catch (err: Error) { console.error(err.message); }48.12 Integration with cloud services
import { uploadData } from 'cloud-sdk'; uploadData<Sensor>(sensorData);48.13 Automation scripts
if(sensorData.value > 50) actuator.send({ action: 'stop' });48.14 Testing IoT devices
const mockSensor: Sensor = { id: 'mock', value: 25, timestamp: new Date() };48.15 Best practices
// Strongly typed device interfaces // Real-time streams with type safety // Secure authentication and logging
interface DataPoint { x: number; y: number; } const sampleData: DataPoint[] = [ { x: 1, y: 10 }, { x: 2, y: 20 } ];49.2 D3.js with TypeScript
import * as d3 from 'd3'; const svg = d3.select<SVGSVGElement, unknown>('#chart');49.3 Chart.js integration
import { Chart, ChartConfiguration } from 'chart.js'; const config: ChartConfiguration = { type: 'bar', data: { labels: ['A','B'], datasets: [{ data: [10,20] }] } };49.4 Plotly.js integration
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
const barData: { labels: string[]; values: number[] } = { labels: ['Jan','Feb'], values: [50, 75] };49.6 Line and area charts
const lineData: { x: number[]; y: number[] } = { x: [1,2,3], y: [10,15,20] };49.7 Pie and donut charts
const pieData: { label: string; value: number }[] = [ { label: 'A', value: 30 }, { label: 'B', value: 70 } ];49.8 Scatter plots
const scatterPoints: { x: number; y: number }[] = [ { x: 1, y: 5 }, { x: 2, y: 10 } ];49.9 Heatmaps
const heatmapData: number[][] = [ [1,2], [3,4] ];49.10 Interactive visualizations
svg.on('click', (event, d: DataPoint) => console.log(d.x, d.y));49.11 Animations in charts
d3.selectAll('rect').transition().duration(1000).attr('height', 50);49.12 Data binding best practices
const updateData = (newData: DataPoint[]) => { /* update chart */ };49.13 Responsive charts
window.addEventListener('resize', () => { /* adjust chart size */ });49.14 Exporting visualizations
const exportChart = (format: 'png' | 'pdf') => { /* export logic */ };49.15 Best practices
// Typed datasets // Responsive and interactive charts // Consistent data structures
interface IUserService { getUser(id: number): Promise<User>; } class UserService implements IUserService { /* implementation */ }50.2 Monorepos vs multiple repos
// tsconfig.json for monorepo with references { "references": [{ "path": "../shared" }] }50.3 Modular architecture
export interface IAuth { login(user: string): boolean; } export class Auth implements IAuth { /* ... */ }50.4 Layered architecture
class UserController { constructor(private service: UserService) {} }50.5 Domain-driven design
class Order { id: string; total: number; constructor(id: string, total: number) { this.id = id; this.total = total; } }50.6 Dependency injection patterns
class AppContainer { getUserService(): IUserService { return new UserService(); } }50.7 Service-oriented design
interface IEmailService { send(email: string): Promise<void>; }50.8 Event-driven architecture
interface IUserCreatedEvent { userId: number; name: string; } eventBus.emit('userCreated', { userId: 1, name: 'Alice' } as IUserCreatedEvent);50.9 Logging and monitoring strategies
interface LogMessage { level: 'info' | 'error'; message: string; timestamp: Date; }50.10 CI/CD pipelines
// 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 build50.11 Code quality standards
/* ESLint + TSConfig setup */ { "extends": ["eslint:recommended","plugin:@typescript-eslint/recommended"] }50.12 Security auditing
if (!input) throw new Error('Invalid input'); // compile-time checked50.13 Documentation practices
/** * Returns user by ID * @param id User ID */ function getUser(id: number): User { /* ... */ }50.14 Scaling strategies
// Example: splitting project into packages with references { "references": [{ "path": "./packages/core" }, { "path": "./packages/ui" }] }50.15 Best enterprise practices
// Type-safe modules, DI, events, logging, CI/CD, documentation