TypeScript Interview Questions with Answers
Most Asked TypeScript Interview Questions for Software Engineer Roles
Introduction
TypeScript is a strongly typed programming language built on JavaScript that brings static typing, modern ES6+ features, and excellent tooling to the world of web development. Developed and maintained by Microsoft, TypeScript is the language of choice for large-scale applications, used by companies like Google, Microsoft, Airbnb, and countless others. This comprehensive guide presents 100+ carefully curated TypeScript interview questions and answers, covering everything from the basics to advanced type system features. You'll master interfaces, type aliases, union/intersection types, generics, classes, access modifiers, decorators, utility types (Partial, Required, Readonly, Pick, Omit, Record, and more), mapped types, conditional types, template literal types, type guards, and real-world coding problems. Whether you're preparing for a frontend role with React or Angular, a full-stack position, or a backend Node.js job, this question bank will solidify your understanding of TypeScript and give you the confidence to ace your interview. Start practicing now and become a TypeScript expert.
Why TypeScript?
- Strong static typing catches errors at compile time, reducing runtime bugs
- Excellent tooling and IDE support – autocompletion, navigation, and refactoring
- Superset of JavaScript – works seamlessly with all existing JavaScript libraries
- Used by major frameworks like React, Angular, and Vue – essential for large-scale apps
- Enables scalable and maintainable enterprise-grade applications
- Growing community, continuous improvements, and high demand in the job market
Most Asked TypeScript Interview Questions
TypeScript is a strongly typed, object-oriented, compiled programming language built on JavaScript. It adds static types to JavaScript.
- Static typing: Type checking at compile time
- ES6+ features: Supports modern JavaScript
- Object-oriented: Classes, interfaces, inheritance
- Tooling: Better IDE support and autocompletion
- Compiles to JavaScript: Runs anywhere JavaScript runs
// Hello World in TypeScript
console.log("Hello, World!");
// Function with types
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("TypeScript"));Variables in TypeScript are declared with let (mutable) and const (immutable), with optional type annotations.
- let: Mutable variable
- const: Immutable constant
- Type inference: Types are inferred automatically
- Type annotations:
let name: string = "Alice" - Primitive types: string, number, boolean, null, undefined
// Variables in TypeScript
// Immutable variable (const)
const immutableVar = "World";
// Mutable variable (let)
let mutableVar = "Hello";
mutableVar = "TypeScript";
// Type inference
let inferred = 42; // TypeScript infers 'number'
// Explicit type annotation
let explicit: number = 10;
// Type annotations
let name: string = "Alice";
let age: number = 25;
let isActive: boolean = true;
let numbers: number[] = [1, 2, 3];
let tuple: [string, number] = ["Alice", 25];
let anyValue: any = "anything";
let unknownValue: unknown = 42;
let voidValue: void = undefined;
let nullValue: null = null;
let undefinedValue: undefined = undefined;
// Display
console.log(immutableVar);
console.log(mutableVar);
console.log(inferred);
console.log(explicit);TypeScript provides primitive types, object types, union types, intersection types, literal types, and more.
- Primitive: string, number, boolean, null, undefined, symbol, bigint
- Array:
number[]orArray<number> - Tuple:
[string, number] - Object:
{ name: string; age: number } - Union:
string | number - Intersection:
Name & Age - Literal:
"active" | "inactive" - Void:
void - Never:
never
// TypeScript Data Types
// Primitive types
let name: string = "Alice";
let age: number = 25;
let isActive: boolean = true;
let nullable: null = null;
let undefinedValue: undefined = undefined;
let bigNumber: bigint = 100n;
let uniqueSymbol: symbol = Symbol("id");
// Array types
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];
// Tuple types
let user: [string, number] = ["Alice", 25];
// Object types
let person: { name: string; age: number } = {
name: "Alice",
age: 25
};
// Union types
let id: string | number = "123";
id = 456;
// Intersection types
interface Name {
name: string;
}
interface Age {
age: number;
}
type Person = Name & Age;
let alice: Person = { name: "Alice", age: 25 };
// Literal types
type Status = "active" | "inactive" | "pending";
let status: Status = "active";
// Void type (functions that don't return)
function logMessage(message: string): void {
console.log(message);
}
// Never type (functions that never return)
function throwError(message: string): never {
throw new Error(message);
}
// Type assertions
let someValue: any = "Hello";
let strLength: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;
// Optional types
interface User {
name: string;
age?: number; // Optional
}
// Readonly types
interface ReadonlyUser {
readonly id: number;
name: string;
}
// Type aliases
type UserId = string | number;
type Callback = (data: any) => void;
// Generic types
function identity<T>(value: T): T {
return value;
}
let result = identity<string>("Hello");
// Utility types
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;
type ReadonlyUser2 = Readonly<User>;
type UserKeys = keyof User;
type UserName = Pick<User, "name">;
type UserWithoutAge = Omit<User, "age">;
// Examples
console.log(name, age, isActive);
console.log(numbers, strings);
console.log(user);
console.log(person);
console.log(id);
console.log(alice);
console.log(status);
logMessage("Hello");
Functions in TypeScript include type annotations for parameters and return values, supporting optional and default parameters.
- Function:
function name(params): returnType { } - Arrow function:
(params): returnType => { } - Optional params:
name?: string - Default params:
name: string = "Guest" - Rest params:
...numbers: number[] - Function overloads: Multiple signatures
// TypeScript Functions
// Basic function with type annotations
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Arrow function
const greetArrow = (name: string): string => {
return `Hello, ${name}!`;
};
// Optional parameters
function greetOptional(name: string, age?: number): string {
if (age) {
return `Hello, ${name}! You are ${age} years old.`;
}
return `Hello, ${name}!`;
}
// Default parameters
function greetDefault(name: string = "Guest"): string {
return `Hello, ${name}!`;
}
// Rest parameters
function sumAll(...numbers: number[]): number {
return numbers.reduce((sum, num) => sum + num, 0);
}
// Function with object parameter
function printUser(user: { name: string; age: number }): void {
console.log(`Name: ${user.name}, Age: ${user.age}`);
}
// Function with interface
interface User {
name: string;
age: number;
email?: string;
}
function createUser(user: User): User {
return {
name: user.name,
age: user.age,
email: user.email || "no-email@example.com"
};
}
// Function overloads
function getData(id: number): string;
function getData(id: string): number;
function getData(id: number | string): string | number {
if (typeof id === "number") {
return `User ID: ${id}`;
} else {
return id.length;
}
}
// Void return type
function logMessage(message: string): void {
console.log(message);
}
// Never return type (function that throws error)
function throwError(message: string): never {
throw new Error(message);
}
// Generic function
function identity<T>(value: T): T {
return value;
}
// Generic with constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Function type
type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
// Higher-order function
function createMultiplier(factor: number): (value: number) => number {
return (value: number) => value * factor;
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
// Async function
async function fetchData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => resolve("Data fetched"), 1000);
});
}
// Examples
console.log(greet("Alice"));
console.log(greetArrow("Bob"));
console.log(greetOptional("Charlie", 30));
console.log(greetDefault());
console.log(sumAll(1, 2, 3, 4, 5));
const user = createUser({ name: "David", age: 25 });
console.log(user);
console.log(getData(123)); // "User ID: 123"
console.log(getData("hello")); // 5
console.log(add(5, 3)); // 8
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Using generic function
console.log(identity<string>("Hello"));
console.log(identity<number>(42));
// Using getProperty
const person = { name: "Alice", age: 25, city: "NYC" };
console.log(getProperty(person, "name")); // "Alice"
Interfaces define the structure of an object. They are used for type-checking and can be extended or implemented.
- Declaration:
interface User { name: string; age: number; } - Optional properties:
email?: string - Readonly properties:
readonly id: number - Function types:
(param: string): void - Index signatures:
[key: string]: any
// Arrays in TypeScript
// Array creation
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: string[] = ["Apple", "Banana", "Orange"];
let mixed: (string | number)[] = [1, "Hello", 3.14];
// Generic array
let genericNumbers: Array<number> = [1, 2, 3];
// Readonly array
let readonlyArray: readonly number[] = [1, 2, 3];
// Access and modify
console.log(numbers[2]);
numbers[2] = 10;
// Array operations
console.log(numbers.length);
numbers.push(6);
numbers.pop();
// Iteration
for (const num of numbers) {
console.log(num);
}
// Array methods with types
const doubled: number[] = numbers.map((num: number): number => num * 2);
const filtered: number[] = numbers.filter((num: number): boolean => num > 2);
const sum: number = numbers.reduce((acc: number, num: number): number => acc + num, 0);
console.log(doubled);
console.log(filtered);
console.log(sum);
// Empty arrays with type
let emptyArray: number[] = [];
let anotherEmpty = new Array<number>();Type aliases are named types that can be used for primitives, unions, intersections, and tuples. Interfaces are limited to object shapes and can be extended/merged.
- Type:
type Name = string,type Status = "active" | "inactive" - Interface:
interface User { name: string } - Extending: interfaces extend with
extends, types use intersection& - Declaration merging: interfaces support it, types do not
- When to use: interface for object shapes, type for everything else
// Collections in TypeScript
// Array (ordered, allows duplicates)
const immutableArray: number[] = [1, 2, 3, 4, 5];
let mutableArray: number[] = [1, 2, 3];
mutableArray.push(4);
mutableArray.splice(1, 1);
// Set (unordered, unique values)
const immutableSet: Set<number> = new Set([1, 2, 3]);
let mutableSet: Set<number> = new Set([1, 2, 3]);
mutableSet.add(4);
mutableSet.delete(2);
// Map (key-value pairs)
const immutableMap: Map<string, string> = new Map([["key1", "value1"], ["key2", "value2"]]);
let mutableMap: Map<string, string> = new Map([["key1", "value1"]]);
mutableMap.set("key2", "value2");
mutableMap.delete("key1");
// Collection operations with types
const numbers: number[] = [1, 2, 3, 4, 5, 6];
const evens: number[] = numbers.filter((num: number): boolean => num % 2 === 0);
const doubled: number[] = numbers.map((num: number): number => num * 2);
const sum: number = numbers.reduce((acc: number, num: number): number => acc + num, 0);
console.log(evens);
console.log(doubled);
console.log(sum);
// Type assertions for collections
const stringSet: Set<string> = new Set(["a", "b", "c"]);
const numberMap: Map<number, string> = new Map([[1, "one"], [2, "two"]]);Union types allow a value to be one of several types. Intersection types combine multiple types into one.
- Union:
string | number - Intersection:
A & B - Type guards: narrow union types with
typeoforinstanceof - Discriminated unions: use a literal property to discriminate
- Intersection for mixins: combine object types
// Classes and Interfaces in TypeScript
// Interface definition
interface Person {
name: string;
age: number;
city?: string; // Optional property
readonly id: number; // Readonly property
greet(): string;
}
// Class implementing interface
class Person implements Person {
readonly id: number;
name: string;
age: number;
city: string;
constructor(id: number, name: string, age: number, city: string = "Unknown") {
this.id = id;
this.name = name;
this.age = age;
this.city = city;
}
greet(): string {
return `Hello, my name is ${this.name}`;
}
// Method with type
updateAge(newAge: number): void {
this.age = newAge;
}
}
// Abstract class
abstract class Animal {
abstract name: string;
abstract makeSound(): string;
move(): string {
return "Moving...";
}
}
class Dog extends Animal {
name: string;
breed: string;
constructor(name: string, breed: string) {
super();
this.name = name;
this.breed = breed;
}
makeSound(): string {
return "Woof!";
}
}
// Usage
const person = new Person(1, "Alice", 25, "NYC");
console.log(person.greet());
console.log(person.id); // Readonly
const dog = new Dog("Rex", "German Shepherd");
console.log(dog.makeSound());
console.log(dog.move());Type assertions inform the compiler about the type of a value when you know more than it does. They do not affect runtime.
- as syntax:
value as string - Angle bracket:
<string>value(not allowed in JSX) - Non-null assertion:
value! - Double assertion:
value as any as string(use sparingly) - Const assertion:
as constfor literal types
// Enums in TypeScript
// Numeric enum
enum Status {
Pending = 1,
Active,
Inactive,
Suspended
}
// String enum
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
// Enum with methods
enum Payment {
Cash = 100,
CreditCard = 200,
PayPal = 300
}
// Heterogeneous enum
enum Mixed {
No = 0,
Yes = "YES"
}
// Enum as type
let status: Status = Status.Active;
let color: Color = Color.Red;
// Enum usage
function handleStatus(status: Status): string {
switch (status) {
case Status.Pending:
return "Pending...";
case Status.Active:
return "Active";
case Status.Inactive:
return "Inactive";
case Status.Suspended:
return "Suspended";
default:
return "Unknown";
}
}
console.log(handleStatus(Status.Active));
console.log(Color.Red);
console.log(Payment.Cash);
// Const enum (inlined)
const enum ConstEnum {
A = 1,
B = 2
}
console.log(ConstEnum.A);
// Enum with computed values
enum Computed {
A = 1,
B = A * 2,
C = B * 2
}Type guards are expressions that perform runtime checks and narrow the type of a variable within a block.
- typeof:
if (typeof value === "string") - instanceof:
if (value instanceof Date) - Custom type predicate:
function isString(value: any): value is string - in operator:
if ("name" in obj) - Discriminated union: check common property
// Null Safety in TypeScript
// Optional types
let nullableString: string | null = "Hello";
let optionalString: string | undefined = "World";
let maybeNumber: number | null | undefined = 42;
// Optional chaining
interface User {
name: string;
address?: {
city: string;
zip?: string;
};
}
const user: User = { name: "Alice" };
// Safe access with optional chaining
const city = user.address?.city ?? "Unknown";
console.log(city);
// Nullish coalescing
const value = null ?? "default";
console.log(value);
// Type guard
function isString(value: any): value is string {
return typeof value === "string";
}
// Using type guard
function processValue(value: string | number): string {
if (isString(value)) {
return `String: ${value}`;
}
return `Number: ${value}`;
}
// Non-null assertion operator
let maybeString: string | null = "hello";
const definitelyString = maybeString!;
// Optional parameters in functions
function greet(name?: string): string {
return `Hello, ${name ?? "Guest"}`;
}
console.log(greet());
console.log(greet("Alice"));Generics allow creating reusable components that work with a variety of types while maintaining type safety.
- Generic function:
function identity<T>(arg: T): T - Generic class:
class Box<T> { value: T } - Generic constraints:
<T extends HasName> - Multiple type parameters:
<K, V> - Default types:
<T = string>
// Control Flow in TypeScript
// If-else
const age: number = 25;
const status: string = age < 18 ? "Minor" : "Adult";
console.log(status);
// If-else-if
const grade: string = "A";
let result: string;
if (grade === "A") {
result = "Excellent";
} else if (grade === "B") {
result = "Good";
} else if (grade === "C") {
result = "Fair";
} else {
result = "Needs Improvement";
}
console.log(result);
// Switch statement
const score: number = 85;
let grade2: string;
switch (true) {
case score >= 90:
grade2 = "A";
break;
case score >= 80:
grade2 = "B";
break;
case score >= 70:
grade2 = "C";
break;
default:
grade2 = "F";
}
console.log(grade2);
// For loop
for (let i: number = 0; i < 5; i++) {
console.log(i);
}
// For-of loop
const items: string[] = ["A", "B", "C"];
for (const item of items) {
console.log(item);
}
// For-in loop
const obj: { [key: string]: string } = { a: "A", b: "B" };
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(`${key}: ${obj[key]}`);
}
}
// While loop
let i: number = 0;
while (i < 5) {
console.log(i);
i++;
}
// Do-while loop
i = 0;
do {
console.log(i);
i--;
} while (i > 0);
// For loop with type guard
const mixedArray: (string | number)[] = [1, "two", 3, "four"];
for (const item of mixedArray) {
if (typeof item === "string") {
console.log(`String: ${item}`);
} else {
console.log(`Number: ${item}`);
}
}Classes in TypeScript extend ES6 classes with type annotations and access modifiers.
- Class:
class Person { name: string; constructor(name: string) { this.name = name; } } - Access modifiers:
public,private,protected - Readonly:
readonly id: number - Abstract classes:
abstract class Animal - Implements:
class Student implements Person
// Inheritance and Polymorphism in TypeScript
// Base class
class Animal {
constructor(public name: string) {}
makeSound(): string {
return "Animal sound";
}
}
// Derived class
class Dog extends Animal {
constructor(name: string, public breed: string) {
super(name);
}
override makeSound(): string {
return "Woof!";
}
}
// Abstract class
abstract class Vehicle {
constructor(public brand: string) {}
abstract start(): string;
stop(): string {
return "Stopped";
}
}
class Car extends Vehicle {
constructor(brand: string, public model: string) {
super(brand);
}
start(): string {
return `${this.brand} ${this.model} started`;
}
}
// Interface inheritance
interface Flyable {
fly(): string;
}
interface Swimmable {
swim(): string;
}
class Duck implements Flyable, Swimmable {
fly(): string {
return "Flying";
}
swim(): string {
return "Swimming";
}
}
// Polymorphism
function makeSound(animal: Animal): string {
return animal.makeSound();
}
// Usage
const dog = new Dog("Rex", "German Shepherd");
console.log(dog.makeSound());
console.log(dog.breed);
const car = new Car("Toyota", "Camry");
console.log(car.start());
console.log(car.stop());
const duck = new Duck();
console.log(duck.fly());
console.log(duck.swim());
console.log(makeSound(dog));Access modifiers control the visibility of class members. TypeScript provides public, private, protected, and readonly.
- public: Accessible anywhere (default)
- private: Accessible only within the class
- protected: Accessible within the class and subclasses
- readonly: Can only be set at declaration or in constructor
- Parameter properties:
constructor(private name: string)
// Properties and Accessors in TypeScript
class Person {
private _name: string;
private _age: number;
private _email: string;
constructor(name: string, age: number, email: string) {
this._name = name;
this._age = age;
this._email = email;
}
// Getter
get name(): string {
return this._name.toUpperCase();
}
// Setter with validation
set name(value: string) {
this._name = value.trim();
}
get age(): number {
return this._age;
}
set age(value: number) {
if (value >= 0) {
this._age = value;
}
}
// Read-only property
get email(): string {
return this._email;
}
// Computed property
get fullName(): string {
return `${this._name} (Age: ${this._age})`;
}
}
// Property with lazy initialization
class LazyProperty {
private _expensiveData: string | null = null;
get expensiveData(): string {
if (this._expensiveData === null) {
console.log("Computing expensive data...");
this._expensiveData = "Expensive Result";
}
return this._expensiveData;
}
}
// Usage
const person = new Person(" Alice ", 25, "alice@example.com");
console.log(person.name); // ALICE
person.name = "Bob";
console.log(person.name); // BOB
person.age = 26;
console.log(person.age);
console.log(person.email);
console.log(person.fullName);
const lazy = new LazyProperty();
console.log(lazy.expensiveData); // Computes
console.log(lazy.expensiveData); // Returns cachedAbstract classes cannot be instantiated directly. They are designed to be extended by subclasses and can contain abstract methods.
- Abstract class:
abstract class Vehicle { abstract start(): void; stop(): void { ... } } - Abstract methods: have no implementation
- Concrete methods: can be used as is
- Constructors: can be called via
super() - Use case: define a common interface for subclasses
// Static Members in TypeScript
class MyClass {
// Static property
static counter: number = 0;
static readonly TAG: string = "MyClass";
// Instance property
id: number;
constructor() {
this.id = ++MyClass.counter;
}
// Static method
static classMethod(): string {
return `Class method called, counter: ${MyClass.counter}`;
}
// Static factory method
static create(): MyClass {
return new MyClass();
}
// Instance method
instanceMethod(): string {
return `Instance ${this.id} method called`;
}
}
// Singleton pattern
class Singleton {
private static instance: Singleton;
private data: string[] = [];
private constructor() {}
static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
addData(item: string): void {
this.data.push(item);
}
getData(): string[] {
return this.data;
}
}
// Usage
console.log(MyClass.TAG);
const obj1 = MyClass.create();
const obj2 = MyClass.create();
console.log(MyClass.classMethod());
console.log(obj1.instanceMethod());
console.log(obj2.instanceMethod());
const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();
singleton1.addData("Hello");
console.log(singleton2.getData()); // ["Hello"]TypeScript supports ES modules with import and export syntax, along with namespace and module resolution options.
- Export:
export const PI = 3.14; export function add() { } - Import:
import { PI, add } from "./math" - Default export:
export default class Calculator - Namespace:
namespace MyLib { export function do() { } } - Module resolution: Node, classic, etc.
// Exception Handling in TypeScript
// Custom error class
class ValidationError extends Error {
constructor(message: string, public field: string) {
super(message);
this.name = "ValidationError";
}
}
// Function that throws
function validateAge(age: number): void {
if (age < 0) {
throw new ValidationError("Age cannot be negative", "age");
}
if (age > 150) {
throw new ValidationError("Invalid age", "age");
}
}
// Function with try-catch
function divide(a: number, b: number): number {
try {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
} catch (error) {
if (error instanceof Error) {
console.log(`Error: ${error.message}`);
}
return 0;
}
}
// Async error handling
async function fetchData(): Promise<string> {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
} catch (error) {
console.error("Fetch error:", error);
throw error;
}
}
// Usage
try {
validateAge(25);
console.log("Age is valid");
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation error: ${error.message} (field: ${error.field})`);
}
}
console.log(divide(10, 2));
console.log(divide(10, 0));
// try-catch-finally
try {
console.log("Trying...");
throw new Error("Error occurred");
} catch (error) {
console.log("Caught error:", error);
} finally {
console.log("Finally block executed");
}
// Result type pattern
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function safeDivide(a: number, b: number): Result<number> {
try {
if (b === 0) {
return { success: false, error: new Error("Division by zero") };
}
return { success: true, data: a / b };
} catch (error) {
return { success: false, error: error as Error };
}
}
const result = safeDivide(10, 2);
if (result.success) {
console.log("Result:", result.data);
} else {
console.log("Error:", result.error.message);
}Decorators are special declarations that can be attached to classes, methods, accessors, properties, or parameters. They are used to modify behavior.
- Class decorator:
@sealed - Method decorator:
@log - Property decorator:
@defaultValue - Parameter decorator:
@inject - Use cases: logging, validation, dependency injection
// Arrow Functions and Closures in TypeScript
// Basic arrow function
const square = (x: number): number => x * x;
// Arrow function with multiple parameters
const add = (a: number, b: number): number => a + b;
// Arrow function with block body
const multiply = (a: number, b: number): number => {
const result = a * b;
return result;
};
// Higher-order arrow function
const operate = (a: number, b: number, operation: (x: number, y: number) => number): number => {
return operation(a, b);
};
// Arrow function with closure
const makeMultiplier = (factor: number): (x: number) => number => {
return (x: number): number => x * factor;
};
// Arrow function with this binding
class Counter {
count: number = 0;
// Arrow function preserves this
increment = (): void => {
this.count++;
};
// Arrow function in callback
startTimer = (): void => {
setInterval(() => {
this.count++;
console.log(this.count);
}, 1000);
};
}
// Usage
console.log(square(5));
console.log(add(5, 3));
console.log(multiply(5, 3));
console.log(operate(6, 7, (a, b) => a * b));
const double = makeMultiplier(2);
console.log(double(5));
// Closure example
function createCounter(): { increment: () => number; getCount: () => number } {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
console.log(counter.increment());
console.log(counter.increment());
console.log(counter.getCount());
// Arrow functions with arrays
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(x => x * 2);
const evens = numbers.filter(x => x % 2 === 0);
const sum = numbers.reduce((acc, x) => acc + x, 0);
console.log(doubled);
console.log(evens);
console.log(sum);Utility types are built-in generic types that transform existing types. They are provided by the TypeScript standard library.
- Partial:
Partial<T>– makes all properties optional - Required:
Required<T>– makes all properties required - Readonly:
Readonly<T>– makes all properties readonly - Pick:
Pick<T, K>– selects a subset of properties - Omit:
Omit<T, K>– omits a set of properties - Record:
Record<K, T>– maps keys to a type - Exclude:
Exclude<T, U>– removes types - Extract:
Extract<T, U>– extracts types - NonNullable:
NonNullable<T>– removes null/undefined - ReturnType:
ReturnType<T>– gets the return type of a function - Parameters:
Parameters<T>– gets tuple of function parameters
// Type Guards and Type Narrowing in TypeScript
// typeof type guard
function processValue(value: string | number): string {
if (typeof value === "string") {
return `String: ${value.toUpperCase()}`;
}
return `Number: ${value.toFixed(2)}`;
}
// instanceof type guard
class Animal { name: string = ""; }
class Dog extends Animal { breed: string = ""; }
function describeAnimal(animal: Animal): string {
if (animal instanceof Dog) {
return `Dog: ${animal.name}, ${animal.breed}`;
}
return `Animal: ${animal.name}`;
}
// Custom type guard
interface User {
name: string;
email: string;
}
interface Admin {
name: string;
role: string;
permissions: string[];
}
function isAdmin(user: User | Admin): user is Admin {
return (user as Admin).role !== undefined;
}
function processUser(user: User | Admin): string {
if (isAdmin(user)) {
return `Admin: ${user.name}, Role: ${user.role}`;
}
return `User: ${user.name}, Email: ${user.email}`;
}
// Discriminated union
interface Square {
kind: "square";
size: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Circle;
function area(shape: Shape): number {
switch (shape.kind) {
case "square":
return shape.size * shape.size;
case "circle":
return Math.PI * shape.radius * shape.radius;
}
}
// Assertion functions
function assertIsString(value: any): asserts value is string {
if (typeof value !== "string") {
throw new Error("Value is not a string");
}
}
function useString(value: any): string {
assertIsString(value);
return value.toUpperCase();
}
// Usage
console.log(processValue("hello"));
console.log(processValue(42));
const dog = new Dog();
dog.name = "Rex";
dog.breed = "German Shepherd";
console.log(describeAnimal(dog));
const user: User = { name: "Alice", email: "alice@example.com" };
const admin: Admin = { name: "Bob", role: "admin", permissions: ["read"] };
console.log(processUser(user));
console.log(processUser(admin));
const square: Square = { kind: "square", size: 5 };
const circle: Circle = { kind: "circle", radius: 3 };
console.log(area(square));
console.log(area(circle));
console.log(useString("hello"));Mapped types transform properties of an existing type by iterating over its keys. They are used to create new types from existing ones.
- Basic mapped:
type Readonly<T> = { readonly [P in keyof T]: T[P] } - Optional:
type Partial<T> = { [P in keyof T]?: T[P] } - Mapping modifiers:
+and-(e.g.,readonly,?) - Key remapping:
[P in keyof T as NewKey]: ... - Template literal types:
[P in keyof T as `get${Capitalize<string & P>}`]: ...
// Utility Types in TypeScript
// Partial
interface User {
id: number;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number; }
// Required
type RequiredUser = Required<PartialUser>;
// { id: number; name: string; email: string; age: number; }
// Readonly
type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string; readonly age: number; }
// Pick
type UserName = Pick<User, "name" | "email">;
// { name: string; email: string; }
// Omit
type UserWithoutId = Omit<User, "id">;
// { name: string; email: string; age: number; }
// Exclude
type Status = "active" | "inactive" | "pending";
type ActiveStatus = Exclude<Status, "inactive" | "pending">;
// "active"
// Extract
type ValidStatus = Extract<Status, "active" | "pending">;
// "active" | "pending"
// NonNullable
type Nullable = string | null | undefined;
type NonNullableString = NonNullable<Nullable>;
// string
// ReturnType
function getUser(): User {
return { id: 1, name: "Alice", email: "alice@example.com", age: 25 };
}
type UserReturnType = ReturnType<typeof getUser>;
// User
// Parameters
function greet(name: string, title: string): string {
return `Hello, ${title} ${name}`;
}
type GreetParams = Parameters<typeof greet>;
// [string, string]
// Record
type UserMap = Record<string, User>;
// { [key: string]: User }
// Usage
const partialUser: PartialUser = { name: "Alice" };
const readonlyUser: ReadonlyUser = { id: 1, name: "Alice", email: "alice@example.com", age: 25 };
// readonlyUser.id = 2; // Error: Cannot assign to 'id' because it is a read-only property
const userMap: UserMap = {
"user1": { id: 1, name: "Alice", email: "alice@example.com", age: 25 },
"user2": { id: 2, name: "Bob", email: "bob@example.com", age: 30 }
};Conditional types select a type based on a condition, like a ternary operator on types. They enable advanced type logic.
- Syntax:
T extends U ? X : Y - Infer:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never - Distributive: conditional types distribute over union types
- Use cases: extracting types, filtering, recursive types
- Recursive:
type Flatten<T> = T extends any[] ? Flatten<T[number]> : T
// Generics in TypeScript
// Generic function
function identity<T>(value: T): T {
return value;
}
// Generic with constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Generic interface
interface Box<T> {
value: T;
getValue(): T;
}
// Generic class
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
// Generic with multiple types
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
// Generic with default type
function createArray<T = string>(length: number, value: T): T[] {
return Array(length).fill(value);
}
// Generic constraints with keyof
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key]);
}
// Usage
console.log(identity<string>("Hello"));
console.log(identity<number>(42));
const user = { id: 1, name: "Alice", age: 25 };
console.log(getProperty(user, "name"));
const stringBox: Box<string> = {
value: "Hello",
getValue() { return this.value; }
};
console.log(stringBox.getValue());
const stack = new Stack<number>();
stack.push(1);
stack.push(2);
console.log(stack.pop());
const merged = merge({ name: "Alice" }, { age: 25 });
console.log(merged);
const stringArray = createArray(3, "Hello");
const numberArray = createArray<number>(3, 42);
const users = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 }
];
const names = pluck(users, "name");
console.log(names);keyof gets the union of keys of a type. typeof gets the type of a value, often used with keyof and typeof together.
- keyof:
type UserKeys = keyof User - typeof:
const obj = { name: "Alice" }; type Obj = typeof obj; - keyof typeof: get keys of an object
- Lookup types:
User["name"] - Generics with keyof:
function getProp<T, K extends keyof T>(obj: T, key: K)
// Decorators in TypeScript
// Class decorator
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
// Method decorator
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with arguments: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned: ${JSON.stringify(result)}`);
return result;
};
}
// Property decorator
function format(target: any, propertyKey: string) {
let value: string;
const getter = function() {
return value;
};
const setter = function(newVal: string) {
value = newVal.toUpperCase();
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
// Accessor decorator
function configurable(value: boolean) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
// Parameter decorator
function required(target: any, propertyKey: string, parameterIndex: number) {
// Implementation
console.log(`Parameter ${parameterIndex} of ${propertyKey} is required`);
}
// Using decorators
@sealed
class User {
@format
name: string;
@log
greet(@required message: string): string {
return `${this.name} says: ${message}`;
}
@configurable(false)
get fullName(): string {
return `User: ${this.name}`;
}
}
// Usage
const user = new User();
user.name = "alice";
console.log(user.name); // ALICE
console.log(user.greet("Hello"));
// Decorator factory
function validate(minLength: number) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
if (args[0] && args[0].length < minLength) {
throw new Error(`${propertyKey} must be at least ${minLength} characters`);
}
return originalMethod.apply(this, args);
};
};
}
class Validator {
@validate(5)
setName(name: string): void {
console.log(`Name set to: ${name}`);
}
}
const validator = new Validator();
validator.setName("Alice");
// validator.setName("Al"); // Throws errorDeclaration merging is the ability to combine multiple declarations of the same name into a single definition. This works for interfaces, namespaces, and enums.
- Interface merging: multiple interfaces with the same name are merged
- Namespace merging: namespaces can be extended
- Enum merging: enums can be merged
- Augmenting modules: add new declarations to existing modules
- Use case: extending third-party types
// Modules in TypeScript
// Exporting
export interface User {
id: number;
name: string;
email: string;
}
export class UserService {
private users: User[] = [];
addUser(user: User): void {
this.users.push(user);
}
getUsers(): User[] {
return this.users;
}
}
export const API_URL = "https://api.example.com";
export default function createUser(name: string, email: string): User {
return {
id: Date.now(),
name,
email
};
}
// Importing (in another file)
/*
import createUser, { User, UserService, API_URL } from './user';
const user = createUser("Alice", "alice@example.com");
const service = new UserService();
service.addUser(user);
console.log(API_URL);
*/
// Namespace (internal modules)
namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export namespace Advanced {
export function multiply(a: number, b: number): number {
return a * b;
}
}
}
// Using namespace
console.log(MathUtils.add(5, 3));
console.log(MathUtils.Advanced.multiply(5, 3));
// Ambient modules (declaration files)
// Example: typings.d.ts
/*
declare module "my-library" {
export function doSomething(): void;
export const version: string;
}
*/
// Module augmentation
declare module './user' {
interface User {
age?: number;
}
}tsconfig.json is the configuration file for TypeScript projects. It controls compiler options, file inclusion, and project settings.
- compilerOptions: target, module, strict, outDir, rootDir, etc.
- Strict flags:
strict,noImplicitAny,strictNullChecks - include/exclude: which files to compile
- references: project references for monorepos
- Paths: path mapping for module resolution
// Async/Await in TypeScript
// Basic async function
async function fetchData(): Promise<string> {
await new Promise(resolve => setTimeout(resolve, 1000));
return "Data loaded";
}
// Async with error handling
async function fetchWithError(): Promise<string> {
try {
const data = await fetchData();
return `Success: ${data}`;
} catch (error) {
return `Error: ${error}`;
}
}
// Async with multiple promises
async function fetchMultiple(): Promise<string[]> {
const [result1, result2] = await Promise.all([
fetchData(),
fetchData()
]);
return [result1, result2];
}
// Async with timeout
async function fetchWithTimeout(timeout: number): Promise<string> {
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), timeout);
});
const dataPromise = fetchData();
return await Promise.race([dataPromise, timeoutPromise]);
}
// Async with retry
async function fetchWithRetry(retries: number): Promise<string> {
for (let i = 0; i < retries; i++) {
try {
return await fetchData();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
throw new Error("All retries failed");
}
// Usage
async function main() {
console.log(await fetchData());
console.log(await fetchWithError());
console.log(await fetchMultiple());
try {
console.log(await fetchWithTimeout(500));
} catch (error) {
console.log("Timeout!");
}
try {
console.log(await fetchWithRetry(3));
} catch (error) {
console.log("All retries failed");
}
}
main();Strict mode enables a set of type-checking rules that catch potential errors. It is recommended for all projects.
- strict: enables all strict family options
- noImplicitAny: raises errors on implicit any
- strictNullChecks: distinguishes null/undefined from other types
- strictFunctionTypes: stricter function parameter checking
- strictPropertyInitialization: ensures class properties are initialized
// Iterators and Generators in TypeScript
// Generator function
function* numberGenerator(): Generator<number> {
yield 1;
yield 2;
yield 3;
}
// Generator with infinite sequence
function* infiniteGenerator(): Generator<number> {
let i = 0;
while (true) {
yield i++;
}
}
// Generator with return value
function* generatorWithReturn(): Generator<number, string, void> {
yield 1;
yield 2;
yield 3;
return "Done";
}
// Generator with input
function* generatorWithInput(): Generator<number, void, number> {
const input = yield 1;
const result = yield input * 2;
return result;
}
// Async generator
async function* asyncGenerator(): AsyncGenerator<number> {
for (let i = 1; i <= 5; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
// Custom iterable
class Range implements Iterable<number> {
constructor(private start: number, private end: number) {}
*[Symbol.iterator](): Iterator<number> {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
// Usage
const gen = numberGenerator();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
const infinite = infiniteGenerator();
console.log(infinite.next().value);
console.log(infinite.next().value);
const withReturn = generatorWithReturn();
console.log(withReturn.next());
console.log(withReturn.next());
console.log(withReturn.next());
console.log(withReturn.next());
const withInput = generatorWithInput();
const first = withInput.next();
const second = withInput.next(5);
console.log(second);
// Async generator
async function processAsyncGenerator() {
for await (const value of asyncGenerator()) {
console.log(value);
}
}
processAsyncGenerator();
// Range iterable
const range = new Range(1, 5);
for (const num of range) {
console.log(num);
}Type inference is the ability of the compiler to automatically deduce types based on context, reducing the need for explicit annotations.
- Variable inference:
let x = 5infersnumber - Function return inference: return type is inferred from the body
- Contextual typing: type of function parameter inferred from usage
- Best common type: when inferring from multiple expressions
- Non-inferable: explicit annotations may be needed
// Observables and Subjects in TypeScript
// Simple Observer implementation
interface Observer<T> {
next(value: T): void;
error(error: any): void;
complete(): void;
}
class Observable<T> {
private observers: Observer<T>[] = [];
subscribe(observer: Observer<T>): () => void {
this.observers.push(observer);
return () => {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
};
}
next(value: T): void {
for (const observer of this.observers) {
observer.next(value);
}
}
error(error: any): void {
for (const observer of this.observers) {
observer.error(error);
}
}
complete(): void {
for (const observer of this.observers) {
observer.complete();
}
}
}
// Subject (hot observable)
class Subject<T> extends Observable<T> {
private value: T | undefined;
next(value: T): void {
this.value = value;
super.next(value);
}
getValue(): T | undefined {
return this.value;
}
}
// BehaviorSubject (Subject with initial value)
class BehaviorSubject<T> extends Subject<T> {
constructor(initialValue: T) {
super();
this.value = initialValue;
}
getValue(): T {
return this.value as T;
}
}
// Usage
const observable = new Observable<number>();
const unsubscribe = observable.subscribe({
next: (value) => console.log(`Observer 1: ${value}`),
error: (error) => console.log(`Error: ${error}`),
complete: () => console.log("Completed")
});
observable.next(1);
observable.next(2);
unsubscribe();
observable.next(3); // Will not be received
const subject = new Subject<string>();
subject.subscribe({
next: (value) => console.log(`Subject Observer: ${value}`)
});
subject.next("Hello");
console.log(subject.getValue());
const behaviorSubject = new BehaviorSubject<number>(0);
behaviorSubject.subscribe({
next: (value) => console.log(`BehaviorSubject: ${value}`)
});
behaviorSubject.next(42);
console.log(behaviorSubject.getValue());Declaration files provide type information for existing JavaScript libraries. They are used to add TypeScript support to non-TypeScript code.
- Declaration file:
*.d.ts - DefinitelyTyped:
@typespackages - Ambient declarations: describe global libraries
- Module declarations: describe module shapes
- Triple-slash references:
/// <reference types="..." />
// Promises in TypeScript
// Basic promise
const promise: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success!");
}, 1000);
});
// Promise with error
const promiseWithError: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Failed!"));
}, 1000);
});
// Promise chaining
function fetchUser(): Promise<{ id: number; name: string }> {
return Promise.resolve({ id: 1, name: "Alice" });
}
function fetchPosts(userId: number): Promise<string[]> {
return Promise.resolve(["Post 1", "Post 2"]);
}
fetchUser()
.then(user => {
console.log(`User: ${user.name}`);
return fetchPosts(user.id);
})
.then(posts => {
console.log(`Posts: ${posts.join(", ")}`);
})
.catch(error => {
console.error(`Error: ${error}`);
});
// Promise.all
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
];
Promise.all(promises)
.then(results => {
console.log("All results:", results);
})
.catch(error => {
console.error("Error:", error);
});
// Promise.race
const racePromises = [
new Promise(resolve => setTimeout(resolve, 1000, "First")),
new Promise(resolve => setTimeout(resolve, 500, "Second")),
new Promise(resolve => setTimeout(resolve, 2000, "Third"))
];
Promise.race(racePromises)
.then(result => {
console.log("Race winner:", result);
});
// Promise.allSettled (ES2020)
Promise.allSettled(promises)
.then(results => {
for (const result of results) {
if (result.status === "fulfilled") {
console.log("Fulfilled:", result.value);
} else {
console.log("Rejected:", result.reason);
}
}
});
// Promise.any (ES2021)
Promise.any(racePromises)
.then(result => {
console.log("Any result:", result);
})
.catch(error => {
console.log("All rejected:", error);
});
// Custom promise type with type safety
type Deferred<T> = {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (reason?: any) => void;
};
function createDeferred<T>(): Deferred<T> {
let resolve!: (value: T) => void;
let reject!: (reason?: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
const deferred = createDeferred<string>();
deferred.promise.then(value => console.log("Deferred:", value));
deferred.resolve("Deferred resolved!");Namespaces group related code under a common name, preventing global scope pollution. They are the older module system, now less common.
- Namespace:
namespace MyLib { export function add() { } } - Nested:
namespace Outer { export namespace Inner { } } - Alias:
import Add = MyLib.add - Ambient:
declare namespace MyLib - Use case: older code, global libraries
// Type Inference and Type Annotations in TypeScript
// Basic type inference
let inferredString = "Hello"; // string
let inferredNumber = 42; // number
let inferredBoolean = true; // boolean
let inferredArray = [1, 2, 3]; // number[]
// Type annotations
let explicitString: string = "Hello";
let explicitNumber: number = 42;
let explicitBoolean: boolean = true;
let explicitArray: number[] = [1, 2, 3];
// Contextual typing
window.onmousedown = function(mouseEvent) {
// mouseEvent is inferred as MouseEvent
console.log(mouseEvent.button);
};
// Type assertion
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;
// Type inference in functions
function add(x: number, y: number) {
return x + y; // Return type inferred as number
}
// Best common type
let mixedArray = [1, "hello", true]; // (string | number | boolean)[]
// Contextual typing with generics
function identity<T>(value: T): T {
return value;
}
let result = identity("Hello"); // result is inferred as string
// Type inference with object literals
let person = {
name: "Alice",
age: 25
};
// person is inferred as { name: string; age: number }
// Type inference with class
class Person {
constructor(public name: string, public age: number) {}
}
let alice = new Person("Alice", 25);
// alice is inferred as Person
// Type inference with union types
let value: string | number = "Hello";
value = 42;
// Type inference with conditional types
type IsString<T> = T extends string ? true : false;
type Result = IsString<"hello">; // true
// Usage
console.log(inferredString);
console.log(explicitString);
console.log(strLength);
console.log(add(5, 3));The satisfies operator (satisfies) ensures that an expression matches a type without affecting its inferred type. It is useful for validation while preserving inference.
- Syntax:
const obj = { name: "Alice" } satisfies HasName - Preserves inference: does not widen the type
- Use case: ensure object matches interface without losing literal types
- Introduced: TypeScript 4.9
- Comparison:
asforces type,satisfieschecks without forcing
// Conditional Types in TypeScript
// Basic conditional type
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Conditional type with infer
type ElementType<T> = T extends (infer U)[] ? U : T;
type C = ElementType<string[]>; // string
type D = ElementType<number>; // number
// Conditional type with union distribution
type ToArray<T> = T extends any ? T[] : never;
type E = ToArray<string | number>; // string[] | number[]
// Type guard with conditional types
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
type F = IsFunction<(x: number) => string>; // true
type G = IsFunction<string>; // false
// Conditional type with keyof
type GetProperty<T, K> = K extends keyof T ? T[K] : never;
interface User {
name: string;
age: number;
}
type H = GetProperty<User, "name">; // string
type I = GetProperty<User, "email">; // never
// Conditional type with recursion
type Flatten<T> = T extends any[] ? T[number] : T;
type J = Flatten<string[]>; // string
type K = Flatten<number>; // number
// Conditional type with never
type NonNullable<T> = T extends null | undefined ? never : T;
type L = NonNullable<string | null>; // string
// Conditional type with tuple
type FirstElement<T> = T extends [infer F, ...any[]] ? F : never;
type M = FirstElement<[1, 2, 3]>; // 1
type N = FirstElement<[]>; // never
// Usage
type IsStringResult = IsString<"hello">;
type ElementTypeResult = ElementType<number[]>;Branded types (nominal types) use a unique tag to distinguish types with the same underlying structure, providing type safety for different domains.
- Branding:
type UserId = string & { __brand: "UserId" } - Factory:
function createUserId(id: string): UserId { return id as UserId; } - Check: ensure values are created through factories
- Use case: preventing mixing of domain values (e.g., UserId vs ProductId)
- Alternative: class with private field
// Mapped Types in TypeScript
// Basic mapped type
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Mapped type with transformation
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type Stringify<T> = {
[P in keyof T]: string;
};
// Mapping over union
type Status = "active" | "inactive" | "pending";
type StatusMap = {
[K in Status]: string;
};
// { active: string; inactive: string; pending: string; }
// Key remapping
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
// Filtering keys
type FilterKeys<T, U> = {
[P in keyof T]: T[P] extends U ? P : never;
}[keyof T];
// Usage
interface User {
id: number;
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;
type NullableUser = Nullable<User>;
type UserName = Pick<User, "name">;
const statusMap: StatusMap = {
active: "Active",
inactive: "Inactive",
pending: "Pending"
};
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; getId: () => number; }TypeScript allows specifying the type of this in functions and methods, improving safety when using callbacks or manipulating context.
- this parameter:
function fn(this: SomeType, param: string) - Method annotation:
method(this: ThisType, arg: any) - Arrow functions: capture lexical
this - Call/apply/bind:
fn.call(context, arg) - ThisType: utility for context type
// Template Literal Types in TypeScript
// Basic template literal type
type Greeting = `Hello, ${string}`;
type H = Greeting; // "Hello, " + string
// Template literal with union
type Color = "red" | "green" | "blue";
type ColorMessage = `Color: ${Color}`;
// "Color: red" | "Color: green" | "Color: blue"
// Template literal with mapped types
type EventName = `on${Capitalize<"click" | "hover" | "focus">}`;
// "onClick" | "onHover" | "onFocus"
// Template literal with conditional types
type Path<T extends string> = T extends `/${infer U}` ? U : T;
type P = Path<"/users">; // "users"
// Template literal with object keys
type ObjectKeys<T> = {
[K in keyof T]: `get${Capitalize<string & K>}`;
}[keyof T];
// Template literal with string manipulation
type UppercaseKeys<T> = {
[K in keyof T as Uppercase<string & K>]: T[K];
};
// Template literal for API endpoints
type ApiMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiPath = `/api/${string}`;
type ApiEndpoint = `${Lowercase<ApiMethod>} ${ApiPath}`;
// "get /api/..." | "post /api/..." | ...
// Template literal for CSS
type CSSUnit = `${number}${"px" | "em" | "rem" | "%"}`;
type CSSProperty = `${string}:${CSSUnit}`;
// Usage
const greeting: Greeting = "Hello, World";
const colorMessage: ColorMessage = "Color: red";
const eventName: EventName = "onClick";
interface User {
id: number;
name: string;
age: number;
}
type UserKeys = ObjectKeys<User>; // "getId" | "getName" | "getAge"
type UppercaseUser = UppercaseKeys<User>;
// { ID: number; NAME: string; AGE: number; }
const apiEndpoint: ApiEndpoint = "get /api/users";
const cssProperty: CSSProperty = "color:red";Template literal types create new string literal types by concatenating strings, using union types and type inference.
- Syntax:
`Hello, ${string}` - Union expansion:
type Status = `${"success" | "error"}` - Infer:
type ExtractName<T> = T extends `Hello, ${infer Name}` ? Name : never - Recursive: can be used recursively
- Use case: constructing CSS class names, event names, etc.
// Type Guards and Assertion Functions
// Type guard with typeof
function isString(value: unknown): value is string {
return typeof value === "string";
}
// Type guard with instanceof
function isDate(value: unknown): value is Date {
return value instanceof Date;
}
// Type guard with custom predicate
interface User {
name: string;
email: string;
}
function isUser(value: any): value is User {
return value && typeof value.name === "string" && typeof value.email === "string";
}
// Type guard for array
function isArray<T>(value: any): value is T[] {
return Array.isArray(value);
}
// Type guard for union
type Animal = Dog | Cat;
interface Dog {
type: "dog";
breed: string;
}
interface Cat {
type: "cat";
color: string;
}
function isDog(animal: Animal): animal is Dog {
return animal.type === "dog";
}
// Assertion function
function assertIsString(value: any): asserts value is string {
if (typeof value !== "string") {
throw new Error("Value is not a string");
}
}
function assertIsNumber(value: any): asserts value is number {
if (typeof value !== "number") {
throw new Error("Value is not a number");
}
}
function assertIsUser(value: any): asserts value is User {
if (!value || typeof value.name !== "string" || typeof value.email !== "string") {
throw new Error("Value is not a User");
}
}
// Usage
const value: unknown = "Hello";
if (isString(value)) {
console.log(value.toUpperCase());
}
const date: unknown = new Date();
if (isDate(date)) {
console.log(date.getFullYear());
}
const user: any = { name: "Alice", email: "alice@example.com" };
if (isUser(user)) {
console.log(user.name);
}
const animal: Animal = { type: "dog", breed: "German Shepherd" };
if (isDog(animal)) {
console.log(animal.breed);
}
function processValue(value: any) {
assertIsString(value);
console.log(value.toUpperCase());
}
function processUser(value: any) {
assertIsUser(value);
console.log(`${value.name} (${value.email})`);
}
processValue("Hello");
processUser({ name: "Alice", email: "alice@example.com" });Advanced type manipulation includes recursive types, type-safe APIs, and metaprogramming using conditional, mapped, and template literal types.
- Recursive types:
type JSONValue = string | number | boolean | null | JSONObject | JSONArray - Type-safe query builders: using mapped types
- Transformations:
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] } - Pattern matching: using conditional types with
infer - Variadic tuple types: spread tuples
// Reflection and Metadata in TypeScript
// Using decorators for metadata
import 'reflect-metadata';
// Metadata keys
const METADATA_KEYS = {
designType: "design:type",
designParamTypes: "design:paramtypes",
designReturnType: "design:returntype",
route: "route",
method: "method"
};
// Route decorator
function Route(path: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata(METADATA_KEYS.route, path, target, propertyKey);
};
}
// Method decorator
function Method(method: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata(METADATA_KEYS.method, method, target, propertyKey);
};
}
// Class decorator for metadata
function Controller(basePath: string) {
return function(target: Function) {
Reflect.defineMetadata("basePath", basePath, target);
};
}
// Using decorators
@Controller("/api")
class UserController {
@Route("/users")
@Method("GET")
getUsers(): User[] {
return [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" }
];
}
@Route("/users/:id")
@Method("GET")
getUser(id: number): User {
return { id, name: "Alice", email: "alice@example.com" };
}
@Route("/users")
@Method("POST")
createUser(user: User): User {
return user;
}
}
// Metadata reflection
function getMethodMetadata(target: any, propertyKey: string) {
const route = Reflect.getMetadata(METADATA_KEYS.route, target, propertyKey);
const method = Reflect.getMetadata(METADATA_KEYS.method, target, propertyKey);
const paramTypes = Reflect.getMetadata(METADATA_KEYS.designParamTypes, target, propertyKey);
const returnType = Reflect.getMetadata(METADATA_KEYS.designReturnType, target, propertyKey);
return { route, method, paramTypes, returnType };
}
// Usage
const controller = new UserController();
const metadata = getMethodMetadata(UserController.prototype, "getUsers");
console.log(metadata);
// Getting class metadata
const basePath = Reflect.getMetadata("basePath", UserController);
console.log(`Base path: ${basePath}`);
// Custom metadata
interface RouteMetadata {
path: string;
method: string;
handler: Function;
}
function getRoutes<T>(target: new (...args: any[]) => T): RouteMetadata[] {
const prototype = target.prototype;
const routes: RouteMetadata[] = [];
const propertyNames = Object.getOwnPropertyNames(prototype);
for (const propertyName of propertyNames) {
if (propertyName === "constructor") continue;
const route = Reflect.getMetadata(METADATA_KEYS.route, prototype, propertyName);
const method = Reflect.getMetadata(METADATA_KEYS.method, prototype, propertyName);
if (route && method) {
routes.push({
path: route,
method: method,
handler: prototype[propertyName]
});
}
}
return routes;
}
const routes = getRoutes(UserController);
console.log(routes);Reverse a string using JavaScript methods or manual iteration with TypeScript types.
- Built-in:
str.split('').reverse().join('') - Spread operator:
[...str].reverse().join('') - Manual: Iterate from end to start
- Return type:
string
// Reverse a string in TypeScript
function reverseString(str: string): string {
return str.split('').reverse().join('');
}
console.log(reverseString("hello")); // "olleh"
// Using spread operator
function reverseStringSpread(str: string): string {
return [...str].reverse().join('');
}
console.log(reverseStringSpread("hello")); // "olleh"
// Manual implementation
function reverseStringManual(str: string): string {
let result = "";
for (let i = str.length - 1; i >= 0; i--) {
result += str[i];
}
return result;
}
console.log(reverseStringManual("hello")); // "olleh"Check if a string is a palindrome using JavaScript methods or two-pointer approach with TypeScript types.
- Built-in:
str === str.split('').reverse().join('') - Two-pointer: Compare from both ends
- Case insensitive:
toLowerCase() - Return type:
boolean
// Check palindrome in TypeScript
function isPalindrome(str: string): boolean {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return cleaned === cleaned.split('').reverse().join('');
}
console.log(isPalindrome("racecar")); // true
console.log(isPalindrome("hello")); // false
// Two-pointer approach
function isPalindromeTwoPointer(str: string): boolean {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
let left = 0;
let right = cleaned.length - 1;
while (left < right) {
if (cleaned[left] !== cleaned[right]) {
return false;
}
left++;
right--;
}
return true;
}
console.log(isPalindromeTwoPointer("A man a plan a canal Panama")); // trueFind maximum value using Math.max or manual iteration with TypeScript.
- Built-in:
Math.max(...arr) - Manual: Iterate and track max
- Empty array: Return
undefined - Return type:
number | undefined
// Find max in array in TypeScript
function findMax(arr: number[]): number | undefined {
return arr.length > 0 ? Math.max(...arr) : undefined;
}
console.log(findMax([1, 5, 3, 9, 2])); // 9
// Manual implementation
function findMaxManual(arr: number[]): number | undefined {
if (arr.length === 0) return undefined;
let maxVal = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] > maxVal) {
maxVal = arr[i];
}
}
return maxVal;
}
console.log(findMaxManual([1, 5, 3, 9, 2])); // 9
// Using reduce
function findMaxReduce(arr: number[]): number {
return arr.reduce((a, b) => Math.max(a, b));
}Remove duplicates using Set or filter method with TypeScript generics.
- Set:
[...new Set(arr)] - Filter:
arr.filter((item, index) => arr.indexOf(item) === index) - Generic:
<T>(arr: T[]): T[] - Complexity: O(n) time
// TypeScript - Remove Duplicates
// Method 1: Using Set (most efficient)
function removeDuplicatesSet<T>(arr: T[]): T[] {
return [...new Set(arr)];
}
// Method 2: Using filter with indexOf
function removeDuplicatesFilter<T>(arr: T[]): T[] {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
// Method 3: Using reduce with object
function removeDuplicatesReduce<T extends string | number>(arr: T[]): T[] {
const seen: Record<string, boolean> = {};
return arr.reduce((acc, item) => {
const key = String(item);
if (!seen[key]) {
seen[key] = true;
acc.push(item);
}
return acc;
}, [] as T[]);
}
// Method 4: Using Map for complex objects
function removeDuplicatesObjects<T extends object>(arr: T[], key: keyof T): T[] {
const seen = new Map<any, boolean>();
return arr.filter(item => {
const value = item[key];
if (!seen.has(value)) {
seen.set(value, true);
return true;
}
return false;
});
}
// Method 5: Using for loop
function removeDuplicatesLoop<T>(arr: T[]): T[] {
const result: T[] = [];
for (let i = 0; i < arr.length; i++) {
if (!result.includes(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
// Example usage
const numbers = [1, 2, 2, 3, 4, 4, 5];
const strings = ["a", "b", "a", "c", "b", "d"];
const objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice" },
{ id: 3, name: "Charlie" }
];
console.log("Numbers (Set):", removeDuplicatesSet(numbers));
console.log("Strings (Filter):", removeDuplicatesFilter(strings));
console.log("Numbers (Reduce):", removeDuplicatesReduce(numbers));
console.log("Objects (Map):", removeDuplicatesObjects(objects, "id"));
console.log("Numbers (Loop):", removeDuplicatesLoop(numbers));
// Generic type with constraints
function removeDuplicatesWithKey<T, K extends keyof T>(arr: T[], key: K): T[] {
const seen = new Set<any>();
return arr.filter(item => {
const value = item[key];
if (!seen.has(value)) {
seen.add(value);
return true;
}
return false;
});
}
// Example with objects using a key
interface Person {
id: number;
name: string;
}
const people: Person[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice" },
{ id: 3, name: "Charlie" }
];
console.log("People by id:", removeDuplicatesWithKey(people, "id"));
// Removing duplicates with custom equality function
function removeDuplicatesWithEquality<T>(
arr: T[],
areEqual: (a: T, b: T) => boolean
): T[] {
const result: T[] = [];
for (const item of arr) {
if (!result.some(existing => areEqual(existing, item))) {
result.push(item);
}
}
return result;
}
// Example with custom equality
const points = [
{ x: 1, y: 2 },
{ x: 3, y: 4 },
{ x: 1, y: 2 },
{ x: 5, y: 6 }
];
const uniquePoints = removeDuplicatesWithEquality(
points,
(a, b) => a.x === b.x && a.y === b.y
);
console.log("Unique points:", uniquePoints);
Merge arrays using spread operator or concat with TypeScript generics.
- Spread:
[...arr1, ...arr2] - concat:
arr1.concat(arr2) - Generic:
<T>(arr1: T[], arr2: T[]): T[] - Unique merge:
[...new Set([...arr1, ...arr2])]
// Merge arrays in TypeScript
function mergeArrays<T>(arr1: T[], arr2: T[]): T[] {
return [...arr1, ...arr2];
}
console.log(mergeArrays([1, 2], [3, 4])); // [1, 2, 3, 4]
// Merge and remove duplicates
function mergeUnique<T>(arr1: T[], arr2: T[]): T[] {
return [...new Set([...arr1, ...arr2])];
}
console.log(mergeUnique([1, 2, 3], [3, 4, 5])); // [1, 2, 3, 4, 5]
// Type-safe merge
function mergeTyped<T extends object, U extends object>(arr1: T[], arr2: U[]): (T | U)[] {
return [...arr1, ...arr2];
}Convert string to number using Number, parseInt, or parseFloat with TypeScript.
- Number:
Number(str) - parseInt:
parseInt(str, 10) - parseFloat:
parseFloat(str) - Return type:
number | null
// Convert string to number in TypeScript
function stringToNumber(str: string): number | null {
const num = Number(str);
return isNaN(num) ? null : num;
}
console.log(stringToNumber("42")); // 42
console.log(stringToNumber("invalid")); // null
// With type safety
function stringToInt(str: string): number | null {
const num = parseInt(str, 10);
return isNaN(num) ? null : num;
}
function stringToFloat(str: string): number | null {
const num = parseFloat(str);
return isNaN(num) ? null : num;
}
console.log(stringToInt("42.5")); // 42
console.log(stringToFloat("42.5")); // 42.5
// With error handling
function safeStringToNumber(str: string): number {
const num = Number(str);
if (isNaN(num)) {
throw new Error(`Invalid number: ${str}`);
}
return num;
}Iterate through object using for...in, Object.keys, or Object.entries with TypeScript.
- for...in:
for (const key in obj) - Object.keys:
Object.keys(obj).forEach - Object.entries:
Object.entries(obj).forEach - Type safety: Use interfaces for typed objects
// Loop through dictionary in TypeScript
interface Dictionary {
[key: string]: any;
}
function loopDict(dict: Dictionary): void {
for (const key in dict) {
if (dict.hasOwnProperty(key)) {
console.log(`${key} => ${dict[key]}`);
}
}
}
const data: Dictionary = { name: "Alice", age: 25, city: "NYC" };
loopDict(data);
// Using Object.keys
function loopDictKeys(dict: Dictionary): void {
Object.keys(dict).forEach(key => {
console.log(`${key} => ${dict[key]}`);
});
}
// Using Object.entries
function loopDictEntries(dict: Dictionary): void {
Object.entries(dict).forEach(([key, value]) => {
console.log(`${key} => ${value}`);
});
}
// Type-safe iteration
interface User {
name: string;
age: number;
city: string;
}
function loopTypedDict(dict: User): void {
const keys: (keyof User)[] = ["name", "age", "city"];
for (const key of keys) {
console.log(`${key} => ${dict[key]}`);
}
}
loopTypedDict({ name: "Alice", age: 25, city: "NYC" });Delay execution using setTimeout, setInterval, or Promises with TypeScript.
- setTimeout:
setTimeout(fn, delay) - Promise:
new Promise(resolve => setTimeout(resolve, delay)) - async/await:
await delay(1000) - Return type:
Promise<void>
// Delay function execution in TypeScript
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Usage with async/await
async function delayedExecution() {
console.log("Start");
await delay(2000);
console.log("After 2 seconds");
}
delayedExecution();
// Using setTimeout with callback
function delayedCallback(ms: number, callback: () => void): void {
setTimeout(callback, ms);
}
delayedCallback(2000, () => {
console.log("After 2 seconds (callback)");
});
// Using setInterval
function intervalExecution(ms: number, callback: () => void): number {
return setInterval(callback, ms);
}
const intervalId = intervalExecution(1000, () => {
console.log("Repeating...");
});
// Clear interval
setTimeout(() => {
clearInterval(intervalId);
console.log("Interval stopped");
}, 5000);
// Promise-based delay with cancellation
function cancellableDelay(ms: number): { promise: Promise<void>; cancel: () => void } {
let timeoutId: NodeJS.Timeout;
let resolve: () => void;
const promise = new Promise<void>((res) => {
resolve = res;
timeoutId = setTimeout(res, ms);
});
return {
promise,
cancel: () => {
clearTimeout(timeoutId);
resolve();
}
};
}
const { promise, cancel } = cancellableDelay(2000);
promise.then(() => console.log("Delayed execution completed"));
setTimeout(cancel, 1000); // Cancel after 1 secondMake HTTP GET requests using fetch with TypeScript types and interfaces.
- fetch:
fetch(url).then(res => res.json()) - async/await:
const response = await fetch(url) - Type safety:
interface User { } - Generic:
fetchData<T>(url: string): Promise<T>
// HTTP GET request in TypeScript
interface User {
id: number;
name: string;
email: string;
}
// Using fetch with async/await
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json() as T;
}
// With error handling
async function fetchUser(id: number): Promise<User | null> {
try {
const user = await fetchData<User>(`https://jsonplaceholder.typicode.com/users/${id}`);
return user;
} catch (error) {
console.error("Error fetching user:", error);
return null;
}
}
// With timeout
async function fetchWithTimeout<T>(url: string, timeout: number): Promise<T> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json() as T;
} finally {
clearTimeout(timeoutId);
}
}
// Usage
async function main() {
const user = await fetchUser(1);
console.log(user);
try {
const user2 = await fetchWithTimeout<User>('https://jsonplaceholder.typicode.com/users/1', 5000);
console.log(user2);
} catch (error) {
console.log('Request timed out');
}
}
main();
// Using axios alternative
// import axios from 'axios';
// async function fetchUserAxios(id: number): Promise<User> {
// const response = await axios.get<User>(`https://jsonplaceholder.typicode.com/users/${id}`);
// return response.data;
// }Create a Deferred using Promises with TypeScript interfaces.
- Deferred interface:
interface Deferred<T> { promise: Promise<T>; resolve: (value: T) => void; reject: (reason?: any) => void; } - Generic:
createDeferred<T>(): Deferred<T> - then method: Handle fulfillment and rejection
- Chain:
then().catch()
// Create a promise-like Deferred in TypeScript
interface Deferred<T> {
promise: Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
}
function createDeferred<T>(): Deferred<T> {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
// Usage
const deferred = createDeferred<string>();
deferred.promise
.then(value => console.log(`Resolved: ${value}`))
.catch(error => console.log(`Rejected: ${error}`));
setTimeout(() => {
deferred.resolve("Success!");
}, 1000);
// With timeout
function createDeferredWithTimeout<T>(timeoutMs: number): Deferred<T> {
const deferred = createDeferred<T>();
const timeoutId = setTimeout(() => {
deferred.reject(new Error("Deferred timeout"));
}, timeoutMs);
const originalResolve = deferred.resolve;
deferred.resolve = (value: T | PromiseLike<T>) => {
clearTimeout(timeoutId);
originalResolve(value);
};
return deferred;
}
const deferredWithTimeout = createDeferredWithTimeout<string>(500);
deferredWithTimeout.promise
.then(value => console.log(`Resolved: ${value}`))
.catch(error => console.log(`Error: ${error.message}`));
// setTimeout(() => deferredWithTimeout.resolve("Success!"), 1000); // Will timeoutCalculate factorial using recursion or iteration with TypeScript types.
- Recursive:
n * factorial(n-1) - Iterative: Loop with multiplication
- Base case:
n <= 1 - Return type:
number
// Factorial in TypeScript
function factorial(n: number): number {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
// Iterative version
function factorialIterative(n: number): number {
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
}
console.log(factorialIterative(5)); // 120
// Using reduce
function factorialReduce(n: number): number {
if (n <= 1) return 1;
return Array.from({ length: n }, (_, i) => i + 1).reduce((a, b) => a * b, 1);
}
console.log(factorialReduce(5)); // 120
// Type-safe with bigint
function factorialBig(n: number): bigint {
if (n <= 1) return 1n;
return BigInt(n) * factorialBig(n - 1);
}
console.log(factorialBig(20).toString());Calculate Fibonacci using recursion, iteration, or memoization with TypeScript.
- Recursive:
fib(n-1) + fib(n-2) - Iterative: Loop with variables
- Memoization: Cache in Map
- Return type:
number
// Fibonacci in TypeScript
function fibonacci(n: number): number {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(8)); // 21
// Iterative version
function fibonacciIterative(n: number): number {
if (n <= 1) {
return n;
}
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
const temp = a + b;
a = b;
b = temp;
}
return b;
}
console.log(fibonacciIterative(8)); // 21
// Memoized version
const fibCache: Map<number, number> = new Map();
function fibonacciMemo(n: number): number {
if (n <= 1) return n;
if (fibCache.has(n)) {
return fibCache.get(n)!;
}
const result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
fibCache.set(n, result);
return result;
}
console.log(fibonacciMemo(8)); // 21
// Generator version
function* fibonacciGenerator(): Generator<number> {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fibGen = fibonacciGenerator();
for (let i = 0; i < 10; i++) {
console.log(fibGen.next().value);
}FizzBuzz using if-else or switch with TypeScript union types.
- Modulo:
i % 15 === 0 - Order: Check 15 first
- Return type:
string[] - Union type:
type FizzBuzzResult = "FizzBuzz" | "Fizz" | "Buzz" | string
// FizzBuzz in TypeScript
function fizzbuzz(n: number): void {
for (let i = 1; i <= n; i++) {
if (i % 15 === 0) {
console.log("FizzBuzz");
} else if (i % 3 === 0) {
console.log("Fizz");
} else if (i % 5 === 0) {
console.log("Buzz");
} else {
console.log(i);
}
}
}
fizzbuzz(15);
// Return as array
function fizzbuzzArray(n: number): string[] {
return Array.from({ length: n }, (_, i) => {
const num = i + 1;
if (num % 15 === 0) return "FizzBuzz";
if (num % 3 === 0) return "Fizz";
if (num % 5 === 0) return "Buzz";
return String(num);
});
}
console.log(fizzbuzzArray(15));
// With type safety
type FizzBuzzResult = "FizzBuzz" | "Fizz" | "Buzz" | string;
function fizzbuzzTyped(n: number): FizzBuzzResult[] {
return Array.from({ length: n }, (_, i) => {
const num = i + 1;
if (num % 15 === 0) return "FizzBuzz";
if (num % 3 === 0) return "Fizz";
if (num % 5 === 0) return "Buzz";
return String(num);
});
}Find missing number using formula or XOR method with TypeScript.
- Formula:
total - sum - XOR: XOR all numbers and indices
- Return type:
number - Edge cases: Empty array, missing first or last
// Find missing number in TypeScript
function findMissing(arr: number[]): number {
const n = arr.length + 1;
const total = n * (n + 1) / 2;
const sum = arr.reduce((a, b) => a + b, 0);
return total - sum;
}
console.log(findMissing([1, 2, 4, 5, 6])); // 3
// Using XOR
function findMissingXOR(arr: number[]): number {
const n = arr.length + 1;
let xorSum = 0;
for (let i = 1; i <= n; i++) {
xorSum ^= i;
}
for (const num of arr) {
xorSum ^= num;
}
return xorSum;
}
console.log(findMissingXOR([1, 2, 4, 5, 6])); // 3
// With type safety
function findMissingTyped(arr: number[]): number | null {
if (arr.length === 0) return null;
const n = arr.length + 1;
const total = n * (n + 1) / 2;
const sum = arr.reduce((a, b) => a + b, 0);
return total - sum;
}Find duplicates using Set or filter method with TypeScript generics.
- Set:
new Set() - Filter:
arr.filter((item, index) => arr.indexOf(item) !== index) - Generic:
<T>(arr: T[]): T[] - Complexity: O(n) time
// Find duplicates in TypeScript
function findDuplicates<T>(arr: T[]): T[] {
const seen = new Set<T>();
const duplicates = new Set<T>();
for (const item of arr) {
if (seen.has(item)) {
duplicates.add(item);
} else {
seen.add(item);
}
}
return Array.from(duplicates);
}
console.log(findDuplicates([1, 2, 3, 2, 4, 3])); // [2, 3]
// Using filter
function findDuplicatesFilter<T>(arr: T[]): T[] {
return arr.filter((item, index) => arr.indexOf(item) !== index);
}
console.log(findDuplicatesFilter([1, 2, 3, 2, 4, 3])); // [2, 3]
// Using Map
function findDuplicatesMap<T>(arr: T[]): T[] {
const counts = new Map<T, number>();
const duplicates: T[] = [];
for (const item of arr) {
const count = counts.get(item) || 0;
counts.set(item, count + 1);
}
for (const [item, count] of counts) {
if (count > 1) {
duplicates.push(item);
}
}
return duplicates;
}
// Type-safe with generic constraint
function findDuplicatesTyped<T extends string | number>(arr: T[]): T[] {
const seen = new Set<T>();
const duplicates = new Set<T>();
for (const item of arr) {
if (seen.has(item)) {
duplicates.add(item);
} else {
seen.add(item);
}
}
return Array.from(duplicates);
}Calculate sum using reduce or manual iteration with TypeScript.
- reduce:
arr.reduce((a, b) => a + b, 0) - Manual: Iterate and accumulate
- Return type:
number - Generic:
<T extends number>(arr: T[]): T
// Sum of array in TypeScript
function sumArray(arr: number[]): number {
return arr.reduce((a, b) => a + b, 0);
}
console.log(sumArray([1, 2, 3, 4, 5])); // 15
// Manual implementation
function sumArrayManual(arr: number[]): number {
let total = 0;
for (const num of arr) {
total += num;
}
return total;
}
console.log(sumArrayManual([1, 2, 3, 4, 5])); // 15
// Using forEach
function sumArrayForEach(arr: number[]): number {
let total = 0;
arr.forEach(num => total += num);
return total;
}
// Generic sum for numeric types
function sumGeneric<T extends number>(arr: T[]): T {
return arr.reduce((a, b) => (a + b) as T, 0 as T);
}
// With type safety
function sumArraySafe(arr: number[]): number {
if (arr.length === 0) return 0;
return arr.reduce((a, b) => a + b, 0);
}Calculate average using sum divided by count with TypeScript.
- Method:
sum / arr.length - Empty array: Return 0
- Return type:
number - Precision: Returns floating point
// Average of array in TypeScript
function averageArray(arr: number[]): number {
if (arr.length === 0) return 0;
return arr.reduce((a, b) => a + b, 0) / arr.length;
}
console.log(averageArray([1, 2, 3, 4, 5])); // 3
// Manual implementation
function averageArrayManual(arr: number[]): number {
if (arr.length === 0) return 0;
let total = 0;
for (const num of arr) {
total += num;
}
return total / arr.length;
}
console.log(averageArrayManual([1, 2, 3, 4, 5])); // 3
// Using forEach
function averageArrayForEach(arr: number[]): number {
if (arr.length === 0) return 0;
let total = 0;
arr.forEach(num => total += num);
return total / arr.length;
}
// With floating point precision
function averageArrayPrecision(arr: number[]): number {
if (arr.length === 0) return 0;
const sum = arr.reduce((a, b) => a + b, 0);
return Number((sum / arr.length).toFixed(2));
}Sort using sort with comparison function and TypeScript generics.
- sort:
arr.sort((a, b) => a - b) - Generic:
<T>(arr: T[]): T[] - Strings:
arr.sort((a, b) => a.localeCompare(b)) - Complexity: O(n log n)
// Sort array ascending in TypeScript
function sortAscending<T>(arr: T[]): T[] {
return [...arr].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
}
console.log(sortAscending([5, 2, 8, 1, 9])); // [1, 2, 5, 8, 9]
// For numbers
function sortAscendingNumbers(arr: number[]): number[] {
return [...arr].sort((a, b) => a - b);
}
console.log(sortAscendingNumbers([5, 2, 8, 1, 9])); // [1, 2, 5, 8, 9]
// For strings
function sortAscendingStrings(arr: string[]): string[] {
return [...arr].sort((a, b) => a.localeCompare(b));
}
console.log(sortAscendingStrings(["banana", "apple", "cherry"])); // ["apple", "banana", "cherry"]
// In-place sorting
function sortAscendingInPlace<T>(arr: T[]): T[] {
return arr.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
}
// Generic sorting
function sortAscendingGeneric<T>(arr: T[], compareFn?: (a: T, b: T) => number): T[] {
if (compareFn) {
return [...arr].sort(compareFn);
}
return [...arr].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
}Sort descending by reversing comparison with TypeScript generics.
- sort:
arr.sort((a, b) => b - a) - Generic:
<T>(arr: T[]): T[] - Strings:
arr.sort((a, b) => b.localeCompare(a)) - Complexity: O(n log n)
// Sort array descending in TypeScript
function sortDescending<T>(arr: T[]): T[] {
return [...arr].sort((a, b) => a > b ? -1 : a < b ? 1 : 0);
}
console.log(sortDescending([5, 2, 8, 1, 9])); // [9, 8, 5, 2, 1]
// For numbers
function sortDescendingNumbers(arr: number[]): number[] {
return [...arr].sort((a, b) => b - a);
}
console.log(sortDescendingNumbers([5, 2, 8, 1, 9])); // [9, 8, 5, 2, 1]
// For strings
function sortDescendingStrings(arr: string[]): string[] {
return [...arr].sort((a, b) => b.localeCompare(a));
}
console.log(sortDescendingStrings(["banana", "apple", "cherry"])); // ["cherry", "banana", "apple"]
// In-place sorting
function sortDescendingInPlace<T>(arr: T[]): T[] {
return arr.sort((a, b) => a > b ? -1 : a < b ? 1 : 0);
}
// Generic with compare function
function sortDescendingGeneric<T>(arr: T[], compareFn?: (a: T, b: T) => number): T[] {
if (compareFn) {
return [...arr].sort((a, b) => -compareFn(a, b));
}
return [...arr].sort((a, b) => a > b ? -1 : a < b ? 1 : 0);
}Flatten nested arrays using recursion, flat, or reduce with TypeScript.
- Recursive: Check if element is array
- flat:
arr.flat(Infinity) - reduce:
reduce((acc, val) => acc.concat(Array.isArray(val) ? flatten(val) : val), []) - Generic:
<T>(arr: any[]): T[]
// Flatten nested array in TypeScript
function flattenArray<T>(arr: any[]): T[] {
const result: T[] = [];
for (const item of arr) {
if (Array.isArray(item)) {
result.push(...flattenArray(item));
} else {
result.push(item);
}
}
return result;
}
console.log(flattenArray([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]
// Using reduce
function flattenArrayReduce<T>(arr: any[]): T[] {
return arr.reduce((acc: T[], val: any) => {
if (Array.isArray(val)) {
acc.push(...flattenArrayReduce(val));
} else {
acc.push(val);
}
return acc;
}, []);
}
console.log(flattenArrayReduce([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]
// Using flat (modern JavaScript)
function flattenArrayFlat<T>(arr: any[]): T[] {
return arr.flat(Infinity);
}
console.log(flattenArrayFlat([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]
// Type-safe flatten
function flattenTyped<T>(arr: T[] | any[]): T[] {
const result: T[] = [];
for (const item of arr) {
if (Array.isArray(item)) {
result.push(...flattenTyped(item));
} else {
result.push(item);
}
}
return result;
}Split array into chunks using slice in loop with TypeScript.
- Loop: Iterate with step size
- slice:
arr.slice(i, i + size) - Generic:
<T>(arr: T[], size: number): T[][] - Edge case: Handle last chunk
// Chunk array in TypeScript
function chunkArray<T>(arr: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
console.log(chunkArray([1, 2, 3, 4, 5, 6], 2)); // [[1, 2], [3, 4], [5, 6]]
// Using while loop
function chunkArrayWhile<T>(arr: T[], size: number): T[][] {
const chunks: T[][] = [];
let i = 0;
while (i < arr.length) {
chunks.push(arr.slice(i, i + size));
i += size;
}
return chunks;
}
console.log(chunkArrayWhile([1, 2, 3, 4, 5, 6], 2)); // [[1, 2], [3, 4], [5, 6]]
// With padding
function chunkArrayPadding<T>(arr: T[], size: number, padValue: T): T[][] {
const chunks = chunkArray(arr, size);
const lastChunk = chunks[chunks.length - 1];
if (lastChunk && lastChunk.length < size) {
while (lastChunk.length < size) {
lastChunk.push(padValue);
}
}
return chunks;
}
console.log(chunkArrayPadding([1, 2, 3, 4, 5], 3, 0)); // [[1, 2, 3], [4, 5, 0]]
// Type-safe chunk
function chunkTyped<T>(arr: T[], size: number): T[][] {
if (size <= 0) throw new Error("Size must be greater than 0");
const chunks: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, Math.min(i + size, arr.length)));
}
return chunks;
}Binary search using while loop or recursion with TypeScript.
- Iterative: While loop with left/right pointers
- Recursive: Recursive function call
- Generic:
<T>(arr: T[], target: T, compare: (a: T, b: T) => number): number - Complexity: O(log n) time
// Binary search in TypeScript
function binarySearch(arr: number[], target: number): number {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
console.log(binarySearch([1, 2, 3, 4, 5, 6, 7], 5)); // 4
// Recursive binary search
function binarySearchRecursive(arr: number[], target: number, left: number = 0, right: number = arr.length - 1): number {
if (left > right) return -1;
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
return binarySearchRecursive(arr, target, mid + 1, right);
} else {
return binarySearchRecursive(arr, target, left, mid - 1);
}
}
console.log(binarySearchRecursive([1, 2, 3, 4, 5, 6, 7], 5)); // 4
// Generic binary search
function binarySearchGeneric<T>(arr: T[], target: T, compare: (a: T, b: T) => number): number {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const cmp = compare(arr[mid], target);
if (cmp === 0) {
return mid;
} else if (cmp < 0) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
const names = ["Alice", "Bob", "Charlie", "David"];
console.log(binarySearchGeneric(names, "Charlie", (a, b) => a.localeCompare(b))); // 2Quick sort using recursion and partitioning with TypeScript generics.
- Algorithm: Choose pivot, partition, recurse
- Generic:
<T>(arr: T[]): T[] - In-place:
quickSortInPlace<T>(arr: T[], low: number, high: number): void - Comparator:
quickSortWithComparator<T>(arr: T[], comparator: (a: T, b: T) => number): T[]
// Quick sort in TypeScript
function quickSort<T>(arr: T[]): T[] {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left: T[] = [];
const right: T[] = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
console.log(quickSort([5, 3, 8, 4, 2, 7, 1, 6])); // [1, 2, 3, 4, 5, 6, 7, 8]
// In-place quick sort
function quickSortInPlace<T>(arr: T[], low: number = 0, high: number = arr.length - 1): void {
if (low < high) {
const pi = partition(arr, low, high);
quickSortInPlace(arr, low, pi - 1);
quickSortInPlace(arr, pi + 1, high);
}
}
function partition<T>(arr: T[], low: number, high: number): number {
const pivot = arr[high];
let i = low - 1;
for (let j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
return i + 1;
}
const numbers = [5, 3, 8, 4, 2, 7, 1, 6];
quickSortInPlace(numbers);
console.log(numbers); // [1, 2, 3, 4, 5, 6, 7, 8]
// Generic with comparator
function quickSortWithComparator<T>(arr: T[], comparator: (a: T, b: T) => number): T[] {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left: T[] = [];
const right: T[] = [];
for (let i = 1; i < arr.length; i++) {
if (comparator(arr[i], pivot) < 0) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSortWithComparator(left, comparator), pivot, ...quickSortWithComparator(right, comparator)];
}Merge sort using divide-and-conquer and merging with TypeScript generics.
- Algorithm: Divide, sort, merge
- Generic:
<T>(arr: T[]): T[] - Comparator:
mergeSortWithComparator<T>(arr: T[], comparator: (a: T, b: T) => number): T[] - Complexity: O(n log n)
// Merge sort in TypeScript
function mergeSort<T>(arr: T[]): T[] {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge<T>(left: T[], right: T[]): T[] {
const result: T[] = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
while (i < left.length) {
result.push(left[i++]);
}
while (j < right.length) {
result.push(right[j++]);
}
return result;
}
console.log(mergeSort([5, 3, 8, 4, 2, 7, 1, 6])); // [1, 2, 3, 4, 5, 6, 7, 8]
// Generic with comparator
function mergeSortWithComparator<T>(arr: T[], comparator: (a: T, b: T) => number): T[] {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSortWithComparator(arr.slice(0, mid), comparator);
const right = mergeSortWithComparator(arr.slice(mid), comparator);
return mergeWithComparator(left, right, comparator);
}
function mergeWithComparator<T>(left: T[], right: T[], comparator: (a: T, b: T) => number): T[] {
const result: T[] = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (comparator(left[i], right[j]) <= 0) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
while (i < left.length) {
result.push(left[i++]);
}
while (j < right.length) {
result.push(right[j++]);
}
return result;
}
const names = ["Charlie", "Alice", "Bob", "David"];
console.log(mergeSortWithComparator(names, (a, b) => a.localeCompare(b))); // ["Alice", "Bob", "Charlie", "David"]Bubble sort with early termination using TypeScript generics.
- Algorithm: Compare adjacent, swap
- Generic:
<T>(arr: T[]): T[] - Optimized:
bubbleSortOptimized<T>(arr: T[]): T[] - Comparator:
bubbleSortGeneric<T>(arr: T[], comparator: (a: T, b: T) => number): T[]
// Bubble sort in TypeScript
function bubbleSort<T>(arr: T[]): T[] {
const sorted = [...arr];
for (let i = 0; i < sorted.length - 1; i++) {
for (let j = 0; j < sorted.length - 1 - i; j++) {
if (sorted[j] > sorted[j + 1]) {
[sorted[j], sorted[j + 1]] = [sorted[j + 1], sorted[j]];
}
}
}
return sorted;
}
console.log(bubbleSort([5, 3, 8, 4, 2, 7, 1, 6])); // [1, 2, 3, 4, 5, 6, 7, 8]
// Optimized bubble sort
function bubbleSortOptimized<T>(arr: T[]): T[] {
const sorted = [...arr];
let swapped = true;
for (let i = 0; i < sorted.length - 1 && swapped; i++) {
swapped = false;
for (let j = 0; j < sorted.length - 1 - i; j++) {
if (sorted[j] > sorted[j + 1]) {
[sorted[j], sorted[j + 1]] = [sorted[j + 1], sorted[j]];
swapped = true;
}
}
}
return sorted;
}
console.log(bubbleSortOptimized([5, 3, 8, 4, 2, 7, 1, 6])); // [1, 2, 3, 4, 5, 6, 7, 8]
// Generic with comparator
function bubbleSortGeneric<T>(arr: T[], comparator: (a: T, b: T) => number): T[] {
const sorted = [...arr];
for (let i = 0; i < sorted.length - 1; i++) {
for (let j = 0; j < sorted.length - 1 - i; j++) {
if (comparator(sorted[j], sorted[j + 1]) > 0) {
[sorted[j], sorted[j + 1]] = [sorted[j + 1], sorted[j]];
}
}
}
return sorted;
}Find common elements using Set or filter with TypeScript generics.
- Set:
new Set(arr2)and filter - Generic:
<T>(arr1: T[], arr2: T[]): T[] - Type constraint:
<T extends string | number> - Complexity: O(n) time with Set
// Intersection of arrays in TypeScript
function intersection<T>(arr1: T[], arr2: T[]): T[] {
const set2 = new Set(arr2);
return arr1.filter(item => set2.has(item));
}
console.log(intersection([1, 2, 3, 4], [3, 4, 5, 6])); // [3, 4]
// Using Set
function intersectionSet<T>(arr1: T[], arr2: T[]): T[] {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
return Array.from(set1).filter(item => set2.has(item));
}
console.log(intersectionSet([1, 2, 3, 4], [3, 4, 5, 6])); // [3, 4]
// Using filter with includes
function intersectionFilter<T>(arr1: T[], arr2: T[]): T[] {
return arr1.filter(item => arr2.includes(item));
}
console.log(intersectionFilter([1, 2, 3, 4], [3, 4, 5, 6])); // [3, 4]
// Generic with type safety
function intersectionTyped<T extends string | number>(arr1: T[], arr2: T[]): T[] {
const set2 = new Set(arr2);
return arr1.filter((item): item is T => set2.has(item));
}Combine arrays with unique elements using Set with TypeScript generics.
- Set:
new Set([...arr1, ...arr2]) - Generic:
<T>(arr1: T[], arr2: T[]): T[] - Preserve order:
arr1.filter(item => !arr2.includes(item)) - Complexity: O(n) time
// Union of arrays in TypeScript
function union<T>(arr1: T[], arr2: T[]): T[] {
return [...new Set([...arr1, ...arr2])];
}
console.log(union([1, 2, 3], [3, 4, 5])); // [1, 2, 3, 4, 5]
// Preserving order
function unionOrder<T>(arr1: T[], arr2: T[]): T[] {
const result = [...arr1];
for (const item of arr2) {
if (!result.includes(item)) {
result.push(item);
}
}
return result;
}
console.log(unionOrder([1, 2, 3], [3, 4, 5])); // [1, 2, 3, 4, 5]
// Using filter
function unionFilter<T>(arr1: T[], arr2: T[]): T[] {
return [...arr1, ...arr2.filter(item => !arr1.includes(item))];
}
console.log(unionFilter([1, 2, 3], [3, 4, 5])); // [1, 2, 3, 4, 5]
// Generic with type safety
function unionTyped<T extends string | number>(arr1: T[], arr2: T[]): T[] {
return Array.from(new Set([...arr1, ...arr2]));
}Find elements in first array not in second using Set with TypeScript.
- Set:
new Set(arr2)and filter - Generic:
<T>(arr1: T[], arr2: T[]): T[] - Symmetric difference:
arr1.filter(x => !set2.has(x)).concat(arr2.filter(x => !set1.has(x))) - Complexity: O(n) time
// Difference of arrays in TypeScript
function difference<T>(arr1: T[], arr2: T[]): T[] {
const set2 = new Set(arr2);
return arr1.filter(item => !set2.has(item));
}
console.log(difference([1, 2, 3, 4], [3, 4, 5, 6])); // [1, 2]
// Symmetric difference
function symmetricDifference<T>(arr1: T[], arr2: T[]): T[] {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
const result: T[] = [];
for (const item of set1) {
if (!set2.has(item)) result.push(item);
}
for (const item of set2) {
if (!set1.has(item)) result.push(item);
}
return result;
}
console.log(symmetricDifference([1, 2, 3], [3, 4, 5])); // [1, 2, 4, 5]
// Using filter
function differenceFilter<T>(arr1: T[], arr2: T[]): T[] {
return arr1.filter(item => !arr2.includes(item));
}
console.log(differenceFilter([1, 2, 3, 4], [3, 4, 5, 6])); // [1, 2]
// Generic with type safety
function differenceTyped<T extends string | number>(arr1: T[], arr2: T[]): T[] {
const set2 = new Set(arr2);
return arr1.filter((item): item is T => !set2.has(item));
}Group objects by property using Map or reduce with TypeScript.
- Map:
new Map<T[keyof T], T[]>() - Generic:
<T extends Record<string, any>>(items: T[], key: keyof T): Map<T[keyof T], T[]> - reduce:
items.reduce((groups, item) => { ... }, {}) - Complexity: O(n) time
// Group by property in TypeScript
interface Item {
type: string;
name: string;
}
function groupByProperty<T extends Record<string, any>>(items: T[], key: keyof T): Map<T[keyof T], T[]> {
const groups = new Map<T[keyof T], T[]>();
for (const item of items) {
const keyValue = item[key];
if (!groups.has(keyValue)) {
groups.set(keyValue, []);
}
groups.get(keyValue)!.push(item);
}
return groups;
}
// Usage
const data: Item[] = [
{ type: "fruit", name: "apple" },
{ type: "fruit", name: "banana" },
{ type: "veg", name: "carrot" }
];
const groups = groupByProperty(data, "type");
for (const [key, items] of groups) {
console.log(`${key}: ${items.map(i => i.name).join(", ")}`);
}
// Using reduce
function groupByPropertyReduce<T extends Record<string, any>>(items: T[], key: keyof T): Record<string, T[]> {
return items.reduce((groups: Record<string, T[]>, item: T) => {
const keyValue = String(item[key]);
if (!groups[keyValue]) {
groups[keyValue] = [];
}
groups[keyValue].push(item);
return groups;
}, {});
}
// Generic group by
function groupBy<T, K extends keyof T>(items: T[], key: K): Map<T[K], T[]> {
return items.reduce((map, item) => {
const keyValue = item[key];
if (!map.has(keyValue)) {
map.set(keyValue, []);
}
map.get(keyValue)!.push(item);
return map;
}, new Map<T[K], T[]>());
}Deep clone using recursion, JSON methods, or structuredClone with TypeScript.
- JSON:
JSON.parse(JSON.stringify(obj)) - Recursive:
deepClone<T>(obj: T): T - structuredClone:
structuredClone(obj) - Type safety: Generic type preservation
// Deep clone object in TypeScript
function deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item)) as any;
}
const cloned: any = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
// Usage
interface User {
name: string;
address: {
city: string;
zip: string;
};
}
const original: User = {
name: "Alice",
address: {
city: "NYC",
zip: "10001"
}
};
const cloned = deepClone(original);
cloned.name = "Bob";
cloned.address.city = "LA";
console.log(original.name); // Alice
console.log(cloned.name); // Bob
console.log(original.address.city); // NYC
console.log(cloned.address.city); // LA
// Using JSON methods (shallow)
function deepCloneJSON<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
// Using structuredClone (modern browsers)
function deepCloneStructured<T>(obj: T): T {
return structuredClone(obj);
}
// Type-safe deep clone
function deepCloneTyped<T>(obj: T): T {
if (obj === null || typeof obj !== "object") return obj;
if (Array.isArray(obj)) return obj.map(item => deepCloneTyped(item)) as any;
const cloned: any = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepCloneTyped(obj[key]);
}
}
return cloned;
}Perform immutable updates using spread or Object.assign with TypeScript.
- Spread:
{...obj, [key]: value} - Generic:
updateImmutable<T>(obj: T, path: string, value: any): T - Nested: Recursive spread updates
- Type safety: Preserve object types
// Immutable update in TypeScript
interface User {
name: string;
age: number;
}
interface State {
user: User;
}
function updateImmutable<T>(obj: T, path: string, value: any): T {
const parts = path.split('.');
if (parts.length === 1) {
return { ...obj, [parts[0]]: value };
}
const first = parts[0];
const rest = parts.slice(1).join('.');
const nested = (obj as any)[first];
return { ...obj, [first]: updateImmutable(nested, rest, value) };
}
// Usage
const state: State = {
user: {
name: "Alice",
age: 25
}
};
const newState = updateImmutable(state, "user.age", 26);
console.log(state.user.age); // 25
console.log(newState.user.age); // 26
// Using spread operator for nested objects
function updateUser(state: State, updates: Partial<User>): State {
return {
...state,
user: {
...state.user,
...updates
}
};
}
const newState2 = updateUser(state, { age: 27 });
console.log(newState2.user.age); // 27
// With immer library (alternative)
// import produce from 'immer';
// const newState3 = produce(state, draft => {
// draft.user.age = 28;
// });
// Generic immutable update
function updateImmutableGeneric<T extends Record<string, any>>(
obj: T,
key: keyof T,
value: any
): T {
return { ...obj, [key]: value };
}Pipe composes functions from left to right with TypeScript generics.
- Implementation:
pipe<T>(value: T, ...fns: ((arg: T) => T)[]): T - Generic: Type-safe function composition
- Async:
pipeAsync<T>(value: T, ...fns: ((arg: T) => Promise<T>)[]): Promise<T> - Direction: Left to right
// Pipe function in TypeScript
function pipe<T>(value: T, ...fns: ((arg: T) => T)[]): T {
return fns.reduce((acc, fn) => fn(acc), value);
}
// Usage
const double = (x: number): number => x * 2;
const addTen = (x: number): number => x + 10;
const square = (x: number): number => x * x;
const result = pipe(5, double, addTen, square);
console.log(result); // (5*2+10)^2 = 400
// With different types
function pipeTyped<T, U, V>(value: T, fn1: (arg: T) => U, fn2: (arg: U) => V): V {
return fn2(fn1(value));
}
const resultTyped = pipeTyped(5, double, addTen);
console.log(resultTyped); // 20
// Async pipe
async function pipeAsync<T>(value: T, ...fns: ((arg: T) => Promise<T>)[]): Promise<T> {
let result = value;
for (const fn of fns) {
result = await fn(result);
}
return result;
}
const asyncDouble = async (x: number): Promise<number> => x * 2;
const asyncAddTen = async (x: number): Promise<number> => x + 10;
pipeAsync(5, asyncDouble, asyncAddTen).then(result => console.log(result)); // 20
// Pipe with custom operator
function pipeOperator<T>(...fns: ((arg: T) => T)[]): (arg: T) => T {
return (value: T) => fns.reduce((acc, fn) => fn(acc), value);
}
const process = pipeOperator(double, addTen, square);
console.log(process(5)); // 400Compose functions from right to left with TypeScript generics.
- Implementation:
compose<T>(...fns: ((arg: T) => T)[]): (arg: T) => T - Generic: Type-safe function composition
- Async:
composeAsync<T>(...fns: ((arg: T) => Promise<T>)[]): (arg: T) => Promise<T> - Direction: Right to left
// Compose function in TypeScript
function compose<T>(...fns: ((arg: T) => T)[]): (arg: T) => T {
return (value: T) => fns.reduceRight((acc, fn) => fn(acc), value);
}
// Usage
const double = (x: number): number => x * 2;
const addTen = (x: number): number => x + 10;
const square = (x: number): number => x * x;
const process = compose(square, addTen, double);
console.log(process(5)); // (5*2+10)^2 = 400
// With different types
function composeTyped<T, U, V>(fn1: (arg: U) => V, fn2: (arg: T) => U): (arg: T) => V {
return (value: T) => fn1(fn2(value));
}
const processTyped = composeTyped(addTen, double);
console.log(processTyped(5)); // 20
// Async compose
async function composeAsync<T>(...fns: ((arg: T) => Promise<T>)[]): (arg: T) => Promise<T> {
return async (value: T) => {
let result = value;
for (let i = fns.length - 1; i >= 0; i--) {
result = await fns[i](result);
}
return result;
};
}
const asyncSquare = async (x: number): Promise<number> => x * x;
const asyncAddTen = async (x: number): Promise<number> => x + 10;
const processAsync = composeAsync(asyncSquare, asyncAddTen);
processAsync(5).then(result => console.log(result)); // (5+10)^2 = 225
// Compose with custom operator
function composeOperator<T>(...fns: ((arg: T) => T)[]): (arg: T) => T {
return (value: T) => fns.reduceRight((acc, fn) => fn(acc), value);
}Cache function results based on arguments using Map with TypeScript.
- Cache:
Map<string, ReturnType<T>> - Generic:
memoize<T extends (...args: any[]) => any>(fn: T): T - TTL:
memoizeWithTTL<T>(fn: T, ttl: number): T - Trade-off: Memory for speed
// Memoization in TypeScript
function memoize<T extends (...args: any[]) => any>(fn: T): T {
const cache = new Map<string, ReturnType<T>>();
return ((...args: Parameters<T>) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
}) as T;
}
// Fibonacci with memoization
const fibMemo = memoize((n: number): number => {
if (n <= 1) return n;
return fibMemo(n - 1) + fibMemo(n - 2);
});
console.log(fibMemo(10)); // 55
// Memoize with multiple arguments
function memoizeMulti<T extends (...args: any[]) => any>(fn: T): T {
const cache = new Map<string, ReturnType<T>>();
return ((...args: Parameters<T>) => {
const key = args.map(arg => JSON.stringify(arg)).join('|');
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
}) as T;
}
const addMemo = memoizeMulti((a: number, b: number): number => {
console.log(`Computing: ${a} + ${b}`);
return a + b;
});
console.log(addMemo(5, 3)); // Computes
console.log(addMemo(5, 3)); // Returns cached
// Memoize with TTL
function memoizeWithTTL<T extends (...args: any[]) => any>(fn: T, ttl: number): T {
const cache = new Map<string, { value: ReturnType<T>; timestamp: number }>();
return ((...args: Parameters<T>) => {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.value;
}
const result = fn(...args);
cache.set(key, { value: result, timestamp: Date.now() });
return result;
}) as T;
}
const expensiveFn = memoizeWithTTL((n: number): number => {
console.log(`Computing expensive: ${n}`);
return n * n;
}, 5000);
console.log(expensiveFn(5)); // Computes
console.log(expensiveFn(5)); // Returns cached (within TTL)Ensure a function is called only once using closure with TypeScript.
- Closure:
let called = false - Generic:
once<T extends (...args: any[]) => any>(fn: T): T - Async:
onceAsync<T extends (...args: any[]) => Promise<any>>(fn: T): T - Use case: Initialization
// Once function in TypeScript
function once<T extends (...args: any[]) => any>(fn: T): T {
let called = false;
let result: ReturnType<T>;
return ((...args: Parameters<T>) => {
if (!called) {
called = true;
result = fn(...args);
}
return result;
}) as T;
}
// Usage
const initialize = once(() => {
console.log("Initialized");
return { id: 1, name: "App" };
});
console.log(initialize()); // Prints "Initialized"
console.log(initialize()); // Returns cached result
// Once with async function
function onceAsync<T extends (...args: any[]) => Promise<any>>(fn: T): T {
let called = false;
let result: Promise<ReturnType<T>>;
return ((...args: Parameters<T>) => {
if (!called) {
called = true;
result = fn(...args);
}
return result;
}) as T;
}
const initializeAsync = onceAsync(async () => {
console.log("Initializing async");
await new Promise(resolve => setTimeout(resolve, 1000));
return { id: 2, name: "App2" };
});
initializeAsync().then(res => console.log(res));
initializeAsync().then(res => console.log(res)); // Returns cached promise
// Once with class
class Once<T> {
private called = false;
private result: T;
private fn: () => T;
constructor(fn: () => T) {
this.fn = fn;
}
call(): T {
if (!this.called) {
this.called = true;
this.result = this.fn();
}
return this.result;
}
}
const onceInstance = new Once(() => {
console.log("Initialized 2");
return "Hello";
});
console.log(onceInstance.call()); // Prints "Initialized 2"
console.log(onceInstance.call()); // Returns cachedDebounce with leading edge using timer and timestamp with TypeScript.
- Timer:
setTimeoutfor delayed execution - Generic:
debounceLeading<T extends (...args: any[]) => any>(delay: number, fn: T): (...args: Parameters<T>) => void - Async:
debounceLeadingAsync<T extends (...args: any[]) => Promise<any>>(delay: number, fn: T): (...args: Parameters<T>) => Promise<ReturnType<T>> - Use case: Search input, API calls
// Debounce with leading edge in TypeScript
function debounceLeading<T extends (...args: any[]) => any>(
delay: number,
fn: T
): (...args: Parameters<T>) => void {
let lastCall = 0;
let timer: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<T>) => {
const now = Date.now();
if (now - lastCall < delay) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
lastCall = Date.now();
fn(...args);
}, delay);
} else {
lastCall = now;
fn(...args);
}
};
}
// Usage
const debounced = debounceLeading(1000, (message: string) => {
console.log(`Executed: ${message}`);
});
debounced("First"); // Executes immediately
debounced("Second"); // Scheduled for later
debounced("Third"); // Scheduled for later
// Debounce with return value
function debounceLeadingWithReturn<T extends (...args: any[]) => any>(
delay: number,
fn: T
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
let lastCall = 0;
let timer: ReturnType<typeof setTimeout> | null = null;
let resolveList: ((value: ReturnType<T>) => void)[] = [];
return (...args: Parameters<T>) => {
return new Promise<ReturnType<T>>((resolve) => {
const now = Date.now();
if (now - lastCall < delay) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
lastCall = Date.now();
const result = fn(...args);
resolveList.forEach(r => r(result));
resolveList = [];
}, delay);
resolveList.push(resolve);
} else {
lastCall = now;
const result = fn(...args);
resolve(result);
}
});
};
}
// Async debounce
function debounceLeadingAsync<T extends (...args: any[]) => Promise<any>>(
delay: number,
fn: T
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
let lastCall = 0;
let timer: ReturnType<typeof setTimeout> | null = null;
let pendingResolves: ((value: ReturnType<T>) => void)[] = [];
return (...args: Parameters<T>) => {
return new Promise<ReturnType<T>>((resolve) => {
const now = Date.now();
if (now - lastCall < delay) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(async () => {
lastCall = Date.now();
const result = await fn(...args);
pendingResolves.forEach(r => r(result));
pendingResolves = [];
}, delay);
pendingResolves.push(resolve);
} else {
lastCall = now;
resolve(fn(...args));
}
});
};
}Throttle with leading edge using timestamp tracking with TypeScript.
- Timestamp: Track last execution time
- Generic:
throttleLeading<T extends (...args: any[]) => any>(delay: number, fn: T): (...args: Parameters<T>) => void - Trailing:
throttleTrailing<T extends (...args: any[]) => any>(delay: number, fn: T): (...args: Parameters<T>) => void - Use case: Scroll events, resize
// Throttle with leading edge in TypeScript
function throttleLeading<T extends (...args: any[]) => any>(
delay: number,
fn: T
): (...args: Parameters<T>) => void {
let lastCall = 0;
return (...args: Parameters<T>) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn(...args);
}
};
}
// Usage
const throttled = throttleLeading(1000, (message: string) => {
console.log(`Executed: ${message}`);
});
throttled("First"); // Executes
throttled("Second"); // Ignored (within 1 second)
throttled("Third"); // Ignored (within 1 second)
// Throttle with trailing edge
function throttleTrailing<T extends (...args: any[]) => any>(
delay: number,
fn: T
): (...args: Parameters<T>) => void {
let lastCall = 0;
let timer: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<T>) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn(...args);
} else if (!timer) {
timer = setTimeout(() => {
timer = null;
lastCall = Date.now();
fn(...args);
}, delay - (now - lastCall));
}
};
}
const throttledTrailing = throttleTrailing(1000, (message: string) => {
console.log(`Executed (trailing): ${message}`);
});
throttledTrailing("First"); // Executes
throttledTrailing("Second"); // Scheduled for later
throttledTrailing("Third"); // Scheduled for later
// Throttle with return value
function throttleLeadingWithReturn<T extends (...args: any[]) => any>(
delay: number,
fn: T
): (...args: Parameters<T>) => ReturnType<T> | undefined {
let lastCall = 0;
let lastResult: ReturnType<T> | undefined;
return (...args: Parameters<T>) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
lastResult = fn(...args);
}
return lastResult;
};
}
// Async throttle
function throttleLeadingAsync<T extends (...args: any[]) => Promise<any>>(
delay: number,
fn: T
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
let lastCall = 0;
let pendingPromise: Promise<ReturnType<T>> | null = null;
return (...args: Parameters<T>) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
pendingPromise = fn(...args);
return pendingPromise;
}
return pendingPromise || Promise.reject(new Error("Throttled"));
};
}Deep equality comparison using recursion for nested structures with TypeScript.
- Recursive: Compare nested structures
- Generic:
deepEqual<T>(obj1: T, obj2: T): boolean - Comparator:
deepEqualWithComparator<T>(obj1: T, obj2: T, comparator: (a: any, b: any) => boolean): boolean - Type safety: Preserve object types
// Deep equal in TypeScript
function deepEqual<T>(obj1: T, obj2: T): boolean {
if (obj1 === obj2) return true;
if (obj1 === null || obj2 === null) return false;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
if (Array.isArray(obj1) !== Array.isArray(obj2)) return false;
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length !== obj2.length) return false;
for (let i = 0; i < obj1.length; i++) {
if (!deepEqual(obj1[i], obj2[i])) return false;
}
return true;
}
const keys1 = Object.keys(obj1) as (keyof T)[];
const keys2 = Object.keys(obj2) as (keyof T)[];
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!obj2.hasOwnProperty(key)) return false;
if (!deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
// Usage
interface User {
name: string;
address: {
city: string;
zip: string;
};
}
const obj1: User = {
name: "Alice",
address: {
city: "NYC",
zip: "10001"
}
};
const obj2: User = {
name: "Alice",
address: {
city: "NYC",
zip: "10001"
}
};
const obj3: User = {
name: "Bob",
address: {
city: "LA",
zip: "90001"
}
};
console.log(deepEqual(obj1, obj2)); // true
console.log(deepEqual(obj1, obj3)); // false
// Deep equal with custom comparator
function deepEqualWithComparator<T>(
obj1: T,
obj2: T,
comparator: (a: any, b: any) => boolean
): boolean {
if (obj1 === obj2) return true;
if (obj1 === null || obj2 === null) return false;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
if (Array.isArray(obj1) !== Array.isArray(obj2)) return false;
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length !== obj2.length) return false;
for (let i = 0; i < obj1.length; i++) {
if (!deepEqualWithComparator(obj1[i], obj2[i], comparator)) return false;
}
return true;
}
const keys1 = Object.keys(obj1) as (keyof T)[];
const keys2 = Object.keys(obj2) as (keyof T)[];
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!obj2.hasOwnProperty(key)) return false;
if (!comparator(obj1[key], obj2[key])) return false;
}
return true;
}Observable pattern with subscribers and notifications using TypeScript generics.
- Observable:
class Observable<T> - Subscribe:
subscribe(observer: Observer<T>): () => void - Subject:
class Subject<T> extends Observable<T> - BehaviorSubject:
class BehaviorSubject<T> extends Subject<T>
// Observable pattern in TypeScript
interface Observer<T> {
next(value: T): void;
error(error: any): void;
complete(): void;
}
class Observable<T> {
private observers: Observer<T>[] = [];
private isCompleted: boolean = false;
subscribe(observer: Observer<T>): () => void {
if (this.isCompleted) {
observer.complete();
return () => {};
}
this.observers.push(observer);
return () => {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
};
}
next(value: T): void {
if (this.isCompleted) return;
for (const observer of this.observers) {
observer.next(value);
}
}
error(error: any): void {
if (this.isCompleted) return;
this.isCompleted = true;
for (const observer of this.observers) {
observer.error(error);
}
this.observers = [];
}
complete(): void {
if (this.isCompleted) return;
this.isCompleted = true;
for (const observer of this.observers) {
observer.complete();
}
this.observers = [];
}
}
// Usage
const observable = new Observable<number>();
const unsubscribe = observable.subscribe({
next: (value) => console.log(`Observer 1: ${value}`),
error: (error) => console.log(`Error: ${error}`),
complete: () => console.log("Completed")
});
observable.next(1);
observable.next(2);
unsubscribe();
observable.next(3); // Will not be received
// Subject (hot observable)
class Subject<T> extends Observable<T> {
private value: T | undefined;
next(value: T): void {
this.value = value;
super.next(value);
}
getValue(): T | undefined {
return this.value;
}
}
// BehaviorSubject
class BehaviorSubject<T> extends Subject<T> {
constructor(initialValue: T) {
super();
this.value = initialValue;
}
getValue(): T {
return this.value as T;
}
}
// Usage
const subject = new Subject<string>();
subject.subscribe({
next: (value) => console.log(`Subject: ${value}`)
});
subject.next("Hello");
console.log(subject.getValue());Singleton pattern using private constructor and static instance with TypeScript.
- Private constructor:
private constructor() { } - Static instance:
private static instance: Singleton - getInstance:
static getInstance(): Singleton - Lazy initialization: Create on first call
// Singleton pattern in TypeScript
class Singleton {
private static instance: Singleton;
private data: string[] = [];
private constructor() {}
static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
addData(item: string): void {
this.data.push(item);
}
getData(): string[] {
return this.data;
}
}
// Usage
const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();
singleton1.addData("Hello");
console.log(singleton2.getData()); // ["Hello"]
console.log(singleton1 === singleton2); // true
// Singleton with lazy initialization
class LazySingleton {
private static instance: LazySingleton | null = null;
private data: Map<string, any> = new Map();
private constructor() {}
static getInstance(): LazySingleton {
if (!LazySingleton.instance) {
LazySingleton.instance = new LazySingleton();
}
return LazySingleton.instance;
}
}
// Singleton with module pattern
class ModuleSingleton {
private static instance: ModuleSingleton;
private constructor() {}
static getInstance(): ModuleSingleton {
return ModuleSingleton.instance || (ModuleSingleton.instance = new ModuleSingleton());
}
}
// Singleton with Symbol
const singletonSymbol = Symbol.for("singleton");
const global = globalThis as any;
if (!global[singletonSymbol]) {
global[singletonSymbol] = new Singleton();
}
const singletonFromSymbol = global[singletonSymbol] as Singleton;Factory pattern using static methods and interfaces with TypeScript.
- Factory method:
static createUser(type: string, name: string): User - Interface:
interface User { name: string; getRole(): string; } - Abstract factory:
GenericFactory<T> - Register:
register(type: string, creator: () => T): void
// Factory pattern in TypeScript
interface User {
name: string;
getRole(): string;
}
class Admin implements User {
name: string;
constructor(name: string) {
this.name = name;
}
getRole(): string {
return "admin";
}
}
class Guest implements User {
name: string;
constructor(name: string) {
this.name = name;
}
getRole(): string {
return "guest";
}
}
class RegularUser implements User {
name: string;
constructor(name: string) {
this.name = name;
}
getRole(): string {
return "regular";
}
}
class UserFactory {
static createUser(type: string, name: string): User {
switch (type) {
case "admin":
return new Admin(name);
case "guest":
return new Guest(name);
default:
return new RegularUser(name);
}
}
}
// Usage
const admin = UserFactory.createUser("admin", "Alice");
const guest = UserFactory.createUser("guest", "Bob");
console.log(`${admin.name} role: ${admin.getRole()}`);
console.log(`${guest.name} role: ${guest.getRole()}`);
// Abstract factory
interface Widget {
draw(): void;
}
class Button implements Widget {
draw(): void {
console.log("Drawing Button");
}
}
class TextField implements Widget {
draw(): void {
console.log("Drawing TextField");
}
}
class WidgetFactory {
static createWidget(type: string): Widget | null {
switch (type) {
case "button":
return new Button();
case "textfield":
return new TextField();
default:
return null;
}
}
}
const button = WidgetFactory.createWidget("button");
button?.draw();
// Generic factory
class GenericFactory<T> {
private creators: Map<string, () => T> = new Map();
register(type: string, creator: () => T): void {
this.creators.set(type, creator);
}
create(type: string): T | null {
const creator = this.creators.get(type);
return creator ? creator() : null;
}
}
const userFactory = new GenericFactory<User>();
userFactory.register("admin", () => new Admin("Admin"));
userFactory.register("guest", () => new Guest("Guest"));
const adminUser = userFactory.create("admin");
console.log(adminUser?.getRole());Strategy pattern using interfaces and composition with TypeScript.
- Strategy interface:
interface PaymentStrategy { pay(amount: number): void; } - Context:
class PaymentContext - Generic:
SortStrategy<T> - Runtime switching:
setStrategy(strategy: PaymentStrategy): void
// Strategy pattern in TypeScript
interface PaymentStrategy {
pay(amount: number): void;
}
class CreditCardStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Paid $${amount} with Credit Card`);
}
}
class PayPalStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Paid $${amount} with PayPal`);
}
}
class CryptoStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Paid $${amount} with Crypto`);
}
}
class PaymentContext {
private strategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
executePayment(amount: number): void {
this.strategy.pay(amount);
}
}
// Usage
const context = new PaymentContext(new CreditCardStrategy());
context.executePayment(100);
context.setStrategy(new PayPalStrategy());
context.executePayment(50);
context.setStrategy(new CryptoStrategy());
context.executePayment(75);
// Strategy with type parameter
interface SortStrategy<T> {
sort(data: T[]): T[];
}
class QuickSort<T> implements SortStrategy<T> {
sort(data: T[]): T[] {
return data.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
}
}
class MergeSort<T> implements SortStrategy<T> {
sort(data: T[]): T[] {
return data.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
}
}
class SortContext<T> {
private strategy: SortStrategy<T>;
constructor(strategy: SortStrategy<T>) {
this.strategy = strategy;
}
setStrategy(strategy: SortStrategy<T>): void {
this.strategy = strategy;
}
executeSort(data: T[]): T[] {
return this.strategy.sort(data);
}
}Observer pattern with subject and observers using TypeScript interfaces.
- Subject:
class Subject - Observer interface:
interface Observer { update(data: string): void; } - Attach/Detach:
attach(observer: Observer): void - Generic:
TypedSubject<T>
// Observer pattern in TypeScript
interface Observer {
update(data: string): void;
}
class Subject {
private observers: Observer[] = [];
private state: string = "";
attach(observer: Observer): void {
this.observers.push(observer);
}
detach(observer: Observer): void {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
setState(state: string): void {
this.state = state;
this.notifyObservers();
}
private notifyObservers(): void {
for (const observer of this.observers) {
observer.update(this.state);
}
}
}
class ConcreteObserver implements Observer {
private name: string;
constructor(name: string) {
this.name = name;
}
update(data: string): void {
console.log(`${this.name} received: ${data}`);
}
}
// Usage
const subject = new Subject();
const observer1 = new ConcreteObserver("Observer1");
const observer2 = new ConcreteObserver("Observer2");
subject.attach(observer1);
subject.attach(observer2);
subject.setState("Hello World");
subject.detach(observer1);
subject.setState("Hello again");
// Observer with type parameter
interface TypedObserver<T> {
update(data: T): void;
}
class TypedSubject<T> {
private observers: TypedObserver<T>[] = [];
private state: T;
constructor(initialState: T) {
this.state = initialState;
}
attach(observer: TypedObserver<T>): void {
this.observers.push(observer);
}
detach(observer: TypedObserver<T>): void {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
setState(state: T): void {
this.state = state;
this.notifyObservers();
}
private notifyObservers(): void {
for (const observer of this.observers) {
observer.update(this.state);
}
}
}
class NumberObserver implements TypedObserver<number> {
private name: string;
constructor(name: string) {
this.name = name;
}
update(data: number): void {
console.log(`${this.name} received: ${data}`);
}
}Decorator pattern using wrapper classes with TypeScript.
- Component:
interface Coffee - Decorator:
class MilkDecorator implements Coffee - Generic:
LoggerDecorator<T> - Chaining: Multiple decorators
// Decorator pattern in TypeScript
interface Coffee {
cost: number;
description: string;
}
class SimpleCoffee implements Coffee {
cost: number = 5.0;
description: string = "Coffee";
}
class MilkDecorator implements Coffee {
private coffee: Coffee;
constructor(coffee: Coffee) {
this.coffee = coffee;
}
get cost(): number {
return this.coffee.cost + 2.0;
}
get description(): string {
return `${this.coffee.description}, Milk`;
}
}
class SugarDecorator implements Coffee {
private coffee: Coffee;
constructor(coffee: Coffee) {
this.coffee = coffee;
}
get cost(): number {
return this.coffee.cost + 1.0;
}
get description(): string {
return `${this.coffee.description}, Sugar`;
}
}
class WhippedCreamDecorator implements Coffee {
private coffee: Coffee;
constructor(coffee: Coffee) {
this.coffee = coffee;
}
get cost(): number {
return this.coffee.cost + 1.5;
}
get description(): string {
return `${this.coffee.description}, Whipped Cream`;
}
}
// Usage
let coffee: Coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
coffee = new WhippedCreamDecorator(coffee);
console.log(coffee.description); // Coffee, Milk, Sugar, Whipped Cream
console.log(coffee.cost); // 9.5
// Generic decorator
class LoggerDecorator<T> {
private target: T;
constructor(target: T) {
this.target = target;
}
get<T>(key: keyof T): any {
console.log(`Getting ${String(key)}`);
return this.target[key];
}
set<T>(key: keyof T, value: any): void {
console.log(`Setting ${String(key)} to ${value}`);
(this.target as any)[key] = value;
}
}
class User {
name: string = "";
age: number = 0;
}
const user = new User();
const loggerUser = new LoggerDecorator(user);
loggerUser.set('name', 'Alice');
console.log(loggerUser.get('name'));Command pattern with execute and undo methods using TypeScript interfaces.
- Command interface:
interface Command { execute(): void; undo(): void; } - Command manager:
class CommandManager - Generic:
GenericCommand<T> - Undo/Redo:
undo(): void
// Command pattern in TypeScript
interface Command {
execute(): void;
undo(): void;
}
class AddCommand implements Command {
private receiver: number[];
private value: number;
constructor(receiver: number[], value: number) {
this.receiver = receiver;
this.value = value;
}
execute(): void {
this.receiver.push(this.value);
}
undo(): void {
const index = this.receiver.indexOf(this.value);
if (index !== -1) {
this.receiver.splice(index, 1);
}
}
}
class CommandManager {
private history: Command[] = [];
private redoStack: Command[] = [];
execute(command: Command): void {
command.execute();
this.history.push(command);
this.redoStack = [];
}
undo(): void {
const command = this.history.pop();
if (command) {
command.undo();
this.redoStack.push(command);
}
}
redo(): void {
const command = this.redoStack.pop();
if (command) {
command.execute();
this.history.push(command);
}
}
}
// Usage
const receiver: number[] = [1, 2, 3];
const manager = new CommandManager();
const addCommand = new AddCommand(receiver, 4);
manager.execute(addCommand);
console.log(receiver); // [1, 2, 3, 4]
manager.undo();
console.log(receiver); // [1, 2, 3]
manager.redo();
console.log(receiver); // [1, 2, 3, 4]
// Generic command
class GenericCommand<T> implements Command {
private receiver: T;
private action: (target: T) => void;
private undoAction: (target: T) => void;
constructor(receiver: T, action: (target: T) => void, undoAction: (target: T) => void) {
this.receiver = receiver;
this.action = action;
this.undoAction = undoAction;
}
execute(): void {
this.action(this.receiver);
}
undo(): void {
this.undoAction(this.receiver);
}
}
class Counter {
value: number = 0;
}
const counter = new Counter();
const incrementCommand = new GenericCommand(
counter,
(c) => c.value++,
(c) => c.value--
);
manager.execute(incrementCommand);
console.log(counter.value); // 1
manager.undo();
console.log(counter.value); // 0Memento pattern for state capture and restoration using TypeScript.
- Originator:
class Originator - Memento:
interface Memento { getState(): string; } - Caretaker:
class Caretaker - Generic:
GenericMemento<T>
// Memento pattern in TypeScript
interface Memento {
getState(): string;
}
class ConcreteMemento implements Memento {
private state: string;
private date: Date;
constructor(state: string) {
this.state = state;
this.date = new Date();
}
getState(): string {
return this.state;
}
getDate(): Date {
return this.date;
}
}
class Originator {
private state: string = "";
setState(state: string): void {
this.state = state;
console.log(`State set to: ${state}`);
}
getState(): string {
return this.state;
}
saveState(): Memento {
return new ConcreteMemento(this.state);
}
restoreState(memento: Memento): void {
this.state = memento.getState();
console.log(`State restored to: ${this.state}`);
}
}
class Caretaker {
private mementos: Memento[] = [];
addMemento(memento: Memento): void {
this.mementos.push(memento);
}
getMemento(index: number): Memento | null {
if (index >= 0 && index < this.mementos.length) {
return this.mementos[index];
}
return null;
}
getHistory(): Memento[] {
return this.mementos;
}
}
// Usage
const originator = new Originator();
const caretaker = new Caretaker();
originator.setState("State 1");
caretaker.addMemento(originator.saveState());
originator.setState("State 2");
caretaker.addMemento(originator.saveState());
originator.setState("State 3");
const memento = caretaker.getMemento(0);
if (memento) {
originator.restoreState(memento);
}
// Generic memento
class GenericMemento<T> {
private state: T;
private timestamp: number;
constructor(state: T) {
this.state = state;
this.timestamp = Date.now();
}
getState(): T {
return this.state;
}
getTimestamp(): number {
return this.timestamp;
}
}
class GenericOriginator<T> {
private state: T;
constructor(initialState: T) {
this.state = initialState;
}
setState(state: T): void {
this.state = state;
}
getState(): T {
return this.state;
}
saveState(): GenericMemento<T> {
return new GenericMemento(this.state);
}
restoreState(memento: GenericMemento<T>): void {
this.state = memento.getState();
}
}Mediator pattern for centralized communication using TypeScript.
- Mediator:
class Mediator - Colleague:
interface Colleague { send(message: string): void; receive(message: string): void; } - Chat room:
class ChatRoom extends Mediator - Generic:
GenericMediator<T>
// Mediator pattern in TypeScript
interface Colleague {
send(message: string): void;
receive(message: string): void;
}
class Mediator {
private colleagues: Colleague[] = [];
register(colleague: Colleague): void {
this.colleagues.push(colleague);
}
send(message: string, sender: Colleague): void {
for (const colleague of this.colleagues) {
if (colleague !== sender) {
colleague.receive(message);
}
}
}
}
class ConcreteColleague implements Colleague {
private name: string;
private mediator: Mediator;
constructor(name: string, mediator: Mediator) {
this.name = name;
this.mediator = mediator;
this.mediator.register(this);
}
send(message: string): void {
console.log(`${this.name} sends: ${message}`);
this.mediator.send(message, this);
}
receive(message: string): void {
console.log(`${this.name} received: ${message}`);
}
}
// Usage
const mediator = new Mediator();
const alice = new ConcreteColleague("Alice", mediator);
const bob = new ConcreteColleague("Bob", mediator);
alice.send("Hello Bob!");
// Chat room mediator
class ChatRoom extends Mediator {
private history: string[] = [];
send(message: string, sender: Colleague): void {
this.history.push(`${(sender as ConcreteColleague).name}: ${message}`);
super.send(message, sender);
}
getHistory(): string[] {
return this.history;
}
}
const chatRoom = new ChatRoom();
const user1 = new ConcreteColleague("User1", chatRoom);
const user2 = new ConcreteColleague("User2", chatRoom);
user1.send("Hello everyone!");
console.log(chatRoom.getHistory());
// Generic mediator
class GenericMediator<T> {
private colleagues: Map<string, T> = new Map();
private messageHandlers: Map<string, (message: any) => void> = new Map();
register(id: string, colleague: T): void {
this.colleagues.set(id, colleague);
}
send(id: string, message: any): void {
const handler = this.messageHandlers.get(id);
if (handler) {
handler(message);
}
}
onMessage(id: string, handler: (message: any) => void): void {
this.messageHandlers.set(id, handler);
}
}Chain of Responsibility using abstract handlers with TypeScript.
- Handler:
interface Handler - AbstractHandler:
abstract class AbstractHandler implements Handler - Chain:
setNext(handler: Handler): Handler - Generic:
class Chain<T>
// Chain of Responsibility in TypeScript
interface Handler {
setNext(handler: Handler): Handler;
handle(request: string): string | null;
}
abstract class AbstractHandler implements Handler {
private nextHandler: Handler | null = null;
setNext(handler: Handler): Handler {
this.nextHandler = handler;
return handler;
}
handle(request: string): string | null {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
class AuthHandler extends AbstractHandler {
handle(request: string): string | null {
if (request.includes("token")) {
console.log("Authentication passed");
return super.handle(request);
}
console.log("Authentication failed");
return null;
}
}
class LoggerHandler extends AbstractHandler {
handle(request: string): string | null {
console.log(`Logging request: ${request}`);
return super.handle(request);
}
}
class PermissionHandler extends AbstractHandler {
handle(request: string): string | null {
if (request.includes("read")) {
console.log("Permission granted");
return super.handle(request);
}
console.log("Permission denied");
return null;
}
}
// Usage
const auth = new AuthHandler();
const logger = new LoggerHandler();
const permission = new PermissionHandler();
auth.setNext(logger).setNext(permission);
auth.handle("token:valid, read:true");
// Generic chain
class Chain<T> {
private handlers: ((request: T) => T | null)[] = [];
addHandler(handler: (request: T) => T | null): void {
this.handlers.push(handler);
}
process(request: T): T | null {
let result: T | null = request;
for (const handler of this.handlers) {
result = handler(result);
if (result === null) {
break;
}
}
return result;
}
}
const chain = new Chain<string>();
chain.addHandler((req) => req.includes("token") ? req : null);
chain.addHandler((req) => req.includes("read") ? req : null);
chain.addHandler((req) => console.log(`Processing: ${req}`) || req);
const result = chain.process("token:valid, read:true");
console.log(result);State pattern for changing behavior with state using TypeScript interfaces.
- State:
interface State { handle(context: Context): void; } - Context:
class Context - Transitions:
setState(state: State): void - State with transitions:
StateWithTransition
// State pattern in TypeScript
interface State {
handle(context: Context): void;
}
class ReadyState implements State {
handle(context: Context): void {
console.log("Ready: Waiting for input");
context.setState(new ProcessingState());
}
}
class ProcessingState implements State {
handle(context: Context): void {
console.log("Processing: Working on task");
context.setState(new CompletedState());
}
}
class CompletedState implements State {
handle(context: Context): void {
console.log("Completed: Task finished");
}
}
class Context {
private state: State;
constructor() {
this.state = new ReadyState();
}
setState(state: State): void {
this.state = state;
}
request(): void {
this.state.handle(this);
}
}
// Usage
const context = new Context();
context.request(); // Ready: Waiting for input
context.request(); // Processing: Working on task
context.request(); // Completed: Task finished
// State with transitions
interface StateWithTransition {
handle(context: StateContext): void;
getTransitions(): string[];
}
class StateContext {
private currentState: StateWithTransition;
private history: string[] = [];
constructor(initialState: StateWithTransition) {
this.currentState = initialState;
}
setState(state: StateWithTransition): void {
this.currentState = state;
}
request(): void {
this.currentState.handle(this);
}
getHistory(): string[] {
return this.history;
}
}
class PendingState implements StateWithTransition {
handle(context: StateContext): void {
console.log("Pending: Waiting for approval");
context.setState(new ProcessingState2());
}
getTransitions(): string[] {
return ["processing", "cancelled"];
}
}
class ProcessingState2 implements StateWithTransition {
handle(context: StateContext): void {
console.log("Processing: Working on task");
context.setState(new CompletedState2());
}
getTransitions(): string[] {
return ["completed", "failed"];
}
}
class CompletedState2 implements StateWithTransition {
handle(context: StateContext): void {
console.log("Completed: Task finished");
}
getTransitions(): string[] {
return [];
}
}Proxy pattern for controlling access using TypeScript.
- Subject:
interface Subject { request(): string; } - Proxy:
class Proxy implements Subject - Virtual proxy:
class VirtualProxy implements Subject - Protection proxy:
class ProtectionProxy implements Subject
// Proxy pattern in TypeScript
interface Subject {
request(): string;
}
class RealSubject implements Subject {
request(): string {
return "RealSubject: Handling request";
}
}
class Proxy implements Subject {
private realSubject: RealSubject | null = null;
request(): string {
if (this.checkAccess()) {
if (!this.realSubject) {
this.realSubject = new RealSubject();
}
const result = this.realSubject.request();
this.logAccess();
return result;
}
return "Proxy: Access denied";
}
private checkAccess(): boolean {
console.log("Proxy: Checking access");
return true;
}
private logAccess(): void {
console.log("Proxy: Logging access");
}
}
// Usage
const proxy = new Proxy();
console.log(proxy.request());
// Virtual proxy (lazy loading)
class VirtualProxy implements Subject {
private realSubject: RealSubject | null = null;
request(): string {
if (!this.realSubject) {
console.log("Proxy: Creating real subject");
this.realSubject = new RealSubject();
}
return this.realSubject.request();
}
}
const virtualProxy = new VirtualProxy();
console.log(virtualProxy.request());
console.log(virtualProxy.request());
// Protection proxy
class ProtectionProxy implements Subject {
private realSubject: RealSubject | null = null;
private user: string;
constructor(user: string) {
this.user = user;
}
request(): string {
if (this.user === "admin") {
if (!this.realSubject) {
this.realSubject = new RealSubject();
}
return this.realSubject.request();
}
return `Proxy: Access denied for user ${this.user}`;
}
}
const adminProxy = new ProtectionProxy("admin");
const guestProxy = new ProtectionProxy("guest");
console.log(adminProxy.request());
console.log(guestProxy.request());
// Generic proxy
class GenericProxy<T> {
private target: T;
private interceptors: ((key: keyof T, ...args: any[]) => any)[] = [];
constructor(target: T) {
this.target = target;
}
addInterceptor(interceptor: (key: keyof T, ...args: any[]) => any): void {
this.interceptors.push(interceptor);
}
getProxy(): T {
return new Proxy(this.target, {
get: (target, key: keyof T, receiver) => {
let result = target[key];
for (const interceptor of this.interceptors) {
result = interceptor(key, ...result);
}
return result;
}
});
}
}Flyweight pattern for sharing objects using TypeScript.
- Flyweight:
interface Flyweight { operation(uniqueState: string): void; } - Factory:
class FlyweightFactory - Character flyweight:
class CharacterFactory - Generic:
GenericFlyweight<T>
// Flyweight pattern in TypeScript
interface Flyweight {
operation(uniqueState: string): void;
}
class ConcreteFlyweight implements Flyweight {
private sharedState: string;
constructor(sharedState: string) {
this.sharedState = sharedState;
}
operation(uniqueState: string): void {
console.log(`Shared: ${this.sharedState}, Unique: ${uniqueState}`);
}
}
class FlyweightFactory {
private flyweights: Map<string, Flyweight> = new Map();
getFlyweight(sharedState: string): Flyweight {
if (!this.flyweights.has(sharedState)) {
this.flyweights.set(sharedState, new ConcreteFlyweight(sharedState));
console.log(`Creating new flyweight for: ${sharedState}`);
}
return this.flyweights.get(sharedState)!;
}
getCount(): number {
return this.flyweights.size;
}
}
// Usage
const factory = new FlyweightFactory();
const fw1 = factory.getFlyweight("state1");
const fw2 = factory.getFlyweight("state1");
const fw3 = factory.getFlyweight("state2");
fw1.operation("unique1");
fw2.operation("unique2");
fw3.operation("unique3");
console.log(`Flyweight count: ${factory.getCount()}`);
// Character flyweight
class Character {
private char: string;
constructor(char: string) {
this.char = char;
}
display(fontSize: number): void {
console.log(`Character: ${this.char}, Size: ${fontSize}`);
}
}
class CharacterFactory {
private characters: Map<string, Character> = new Map();
getCharacter(char: string): Character {
if (!this.characters.has(char)) {
this.characters.set(char, new Character(char));
}
return this.characters.get(char)!;
}
}
const charFactory = new CharacterFactory();
const text = "hello";
for (const char of text) {
const character = charFactory.getCharacter(char);
character.display(12);
}
// Generic flyweight
class GenericFlyweight<T> {
private instances: Map<string, T> = new Map();
private creator: (key: string) => T;
constructor(creator: (key: string) => T) {
this.creator = creator;
}
get(key: string): T {
if (!this.instances.has(key)) {
this.instances.set(key, this.creator(key));
}
return this.instances.get(key)!;
}
getCount(): number {
return this.instances.size;
}
}Bridge pattern for separating abstraction from implementation using TypeScript.
- Implementation:
interface Implementation - Abstraction:
class Abstraction - Extended:
class ExtendedAbstraction extends Abstraction - Generic:
GenericImplementation<T>
// Bridge pattern in TypeScript
interface Implementation {
operationImpl(): string;
}
class ConcreteImplementationA implements Implementation {
operationImpl(): string {
return "ConcreteImplementationA: Operation";
}
}
class ConcreteImplementationB implements Implementation {
operationImpl(): string {
return "ConcreteImplementationB: Operation";
}
}
class Abstraction {
protected implementation: Implementation;
constructor(implementation: Implementation) {
this.implementation = implementation;
}
operation(): string {
return `Abstraction: Additional logic - ${this.implementation.operationImpl()}`;
}
}
class ExtendedAbstraction extends Abstraction {
operation(): string {
return `ExtendedAbstraction: More logic - ${this.implementation.operationImpl()}`;
}
}
// Usage
const implA = new ConcreteImplementationA();
const implB = new ConcreteImplementationB();
const abstraction1 = new Abstraction(implA);
const abstraction2 = new Abstraction(implB);
console.log(abstraction1.operation());
console.log(abstraction2.operation());
const extended = new ExtendedAbstraction(implA);
console.log(extended.operation());
// Generic bridge
interface GenericImplementation<T> {
process(data: T): T;
}
class StringImplementation implements GenericImplementation<string> {
process(data: string): string {
return data.toUpperCase();
}
}
class NumberImplementation implements GenericImplementation<number> {
process(data: number): number {
return data * 2;
}
}
class GenericAbstraction<T> {
protected implementation: GenericImplementation<T>;
constructor(implementation: GenericImplementation<T>) {
this.implementation = implementation;
}
process(data: T): T {
return this.implementation.process(data);
}
}
const stringImpl = new StringImplementation();
const numberImpl = new NumberImplementation();
const stringAbstraction = new GenericAbstraction(stringImpl);
const numberAbstraction = new GenericAbstraction(numberImpl);
console.log(stringAbstraction.process("hello")); // HELLO
console.log(numberAbstraction.process(21)); // 42Adapter pattern for converting interfaces using TypeScript.
- Target:
interface Target { request(): string; } - Adaptee:
class Adaptee - Adapter:
class Adapter implements Target - Generic:
GenericAdapter<T>
// Adapter pattern in TypeScript
interface Target {
request(): string;
}
class Adaptee {
specificRequest(): string {
return "Adaptee: Specific Request";
}
}
class Adapter implements Target {
private adaptee: Adaptee;
constructor(adaptee: Adaptee) {
this.adaptee = adaptee;
}
request(): string {
return this.adaptee.specificRequest();
}
}
// Usage
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
console.log(adapter.request());
// Class adapter (multiple inheritance simulation)
class ClassAdapter extends Adaptee implements Target {
request(): string {
return this.specificRequest();
}
}
const classAdapter = new ClassAdapter();
console.log(classAdapter.request());
// Object adapter with type conversion
interface ModernInterface {
getData(): string;
}
class LegacySystem {
getLegacyData(): string {
return "Legacy data";
}
}
class LegacyToModernAdapter implements ModernInterface {
private legacy: LegacySystem;
constructor(legacy: LegacySystem) {
this.legacy = legacy;
}
getData(): string {
const data = this.legacy.getLegacyData();
return `Modern: ${data}`;
}
}
const legacy = new LegacySystem();
const modern = new LegacyToModernAdapter(legacy);
console.log(modern.getData());
// Generic adapter
interface Target<T> {
request(data: T): T;
}
class GenericAdaptee<T> {
specificRequest(data: T): T {
return data;
}
}
class GenericAdapter<T> implements Target<T> {
private adaptee: GenericAdaptee<T>;
constructor(adaptee: GenericAdaptee<T>) {
this.adaptee = adaptee;
}
request(data: T): T {
return this.adaptee.specificRequest(data);
}
}
const genericAdaptee = new GenericAdaptee<string>();
const genericAdapter = new GenericAdapter(genericAdaptee);
console.log(genericAdapter.request("Hello"));Facade pattern for simplifying subsystems using TypeScript.
- Subsystems:
class SubsystemA, SubsystemB, SubsystemC - Facade:
class Facade - Simplified operation:
operation(): string - Generic:
DatabaseFacade
// Facade pattern in TypeScript
class SubsystemA {
operationA(): string {
return "SubsystemA: Operation";
}
}
class SubsystemB {
operationB(): string {
return "SubsystemB: Operation";
}
}
class SubsystemC {
operationC(): string {
return "SubsystemC: Operation";
}
}
class Facade {
private subsystemA: SubsystemA;
private subsystemB: SubsystemB;
private subsystemC: SubsystemC;
constructor() {
this.subsystemA = new SubsystemA();
this.subsystemB = new SubsystemB();
this.subsystemC = new SubsystemC();
}
operation(): string {
const results = [
this.subsystemA.operationA(),
this.subsystemB.operationB(),
this.subsystemC.operationC()
];
return `Facade: Complex operation - ${results.join(", ")}`;
}
simplifiedOperation(): string {
return `Facade: Simplified operation - ${this.subsystemA.operationA()}`;
}
}
// Usage
const facade = new Facade();
console.log(facade.operation());
console.log(facade.simplifiedOperation());
// Generic facade
interface Database {
connect(): string;
query(sql: string): string;
disconnect(): string;
}
class MySQLDatabase implements Database {
connect(): string {
return "Connected to MySQL";
}
query(sql: string): string {
return `MySQL executing: ${sql}`;
}
disconnect(): string {
return "Disconnected from MySQL";
}
}
class PostgreSQLDatabase implements Database {
connect(): string {
return "Connected to PostgreSQL";
}
query(sql: string): string {
return `PostgreSQL executing: ${sql}`;
}
disconnect(): string {
return "Disconnected from PostgreSQL";
}
}
class DatabaseFacade {
private db: Database;
constructor(db: Database) {
this.db = db;
}
executeQuery(sql: string): string {
const connect = this.db.connect();
const result = this.db.query(sql);
const disconnect = this.db.disconnect();
return `${connect}
${result}
${disconnect}`;
}
}
const mysql = new MySQLDatabase();
const postgres = new PostgreSQLDatabase();
const mysqlFacade = new DatabaseFacade(mysql);
const postgresFacade = new DatabaseFacade(postgres);
console.log(mysqlFacade.executeQuery("SELECT * FROM users"));
console.log(postgresFacade.executeQuery("SELECT * FROM users"));Composite pattern for tree structures using TypeScript.
- Component:
interface Component { operation(): string; } - Leaf:
class Leaf implements Component - Composite:
class Composite implements Component - Generic:
GenericComposite<T>
// Composite pattern in TypeScript
interface Component {
operation(): string;
}
class Leaf implements Component {
private name: string;
constructor(name: string) {
this.name = name;
}
operation(): string {
return `Leaf ${this.name}: Operation`;
}
}
class Composite implements Component {
private name: string;
private children: Component[] = [];
constructor(name: string) {
this.name = name;
}
add(component: Component): void {
this.children.push(component);
}
remove(component: Component): void {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
}
}
operation(): string {
const results = this.children.map(child => child.operation());
return `Composite ${this.name}: [${results.join(", ")}]`;
}
}
// Usage
const leaf1 = new Leaf("A");
const leaf2 = new Leaf("B");
const leaf3 = new Leaf("C");
const composite1 = new Composite("Composite1");
composite1.add(leaf1);
composite1.add(leaf2);
const composite2 = new Composite("Root");
composite2.add(composite1);
composite2.add(leaf3);
console.log(composite2.operation());
// Generic composite
interface GenericComponent<T> {
operation(): T;
}
class GenericLeaf<T> implements GenericComponent<T> {
private data: T;
constructor(data: T) {
this.data = data;
}
operation(): T {
return this.data;
}
}
class GenericComposite<T> implements GenericComponent<T> {
private children: GenericComponent<T>[] = [];
private combine: (results: T[]) => T;
constructor(combine: (results: T[]) => T) {
this.combine = combine;
}
add(component: GenericComponent<T>): void {
this.children.push(component);
}
operation(): T {
const results = this.children.map(child => child.operation());
return this.combine(results);
}
}
const sumComposite = new GenericComposite<number>((results) =>
results.reduce((a, b) => a + b, 0)
);
sumComposite.add(new GenericLeaf(1));
sumComposite.add(new GenericLeaf(2));
sumComposite.add(new GenericLeaf(3));
console.log(sumComposite.operation()); // 6Visitor pattern for adding operations without modifying elements using TypeScript.
- Visitor:
interface Visitor - Element:
interface Element { accept(visitor: Visitor): string; } - Concrete visitor:
class ConcreteVisitor implements Visitor - Generic:
TypedVisitor<T>
// Visitor pattern in TypeScript
interface Visitor {
visitElementA(element: ElementA): string;
visitElementB(element: ElementB): string;
}
interface Element {
accept(visitor: Visitor): string;
}
class ElementA implements Element {
private data: string;
constructor(data: string) {
this.data = data;
}
accept(visitor: Visitor): string {
return visitor.visitElementA(this);
}
getData(): string {
return this.data;
}
}
class ElementB implements Element {
private data: string;
constructor(data: string) {
this.data = data;
}
accept(visitor: Visitor): string {
return visitor.visitElementB(this);
}
getData(): string {
return this.data;
}
}
class ConcreteVisitor implements Visitor {
visitElementA(element: ElementA): string {
return `Visiting ElementA with data: ${element.getData()}`;
}
visitElementB(element: ElementB): string {
return `Visiting ElementB with data: ${element.getData()}`;
}
}
// Usage
const visitor = new ConcreteVisitor();
const elementA = new ElementA("A data");
const elementB = new ElementB("B data");
console.log(elementA.accept(visitor));
console.log(elementB.accept(visitor));
// Visitor with type parameter
interface TypedVisitor<T> {
visitElementA(element: ElementA): T;
visitElementB(element: ElementB): T;
}
class StringVisitor implements TypedVisitor<string> {
visitElementA(element: ElementA): string {
return `StringVisitor: ElementA - ${element.getData()}`;
}
visitElementB(element: ElementB): string {
return `StringVisitor: ElementB - ${element.getData()}`;
}
}
class NumberVisitor implements TypedVisitor<number> {
visitElementA(element: ElementA): number {
return element.getData().length;
}
visitElementB(element: ElementB): number {
return element.getData().length;
}
}
const stringVisitor = new StringVisitor();
const numberVisitor = new NumberVisitor();
console.log(elementA.accept(stringVisitor));
console.log(elementA.accept(numberVisitor));
console.log(elementB.accept(stringVisitor));
console.log(elementB.accept(numberVisitor));Iterator pattern for sequential access using TypeScript generics.
- Iterator:
interface Iterator<T> { next(): T | null; hasNext(): boolean; reset(): void; } - Collection:
class CustomCollection<T> - Fibonacci iterator:
class FibonacciIterator implements Iterator<number> - Step iterator:
class StepIterator<T> implements Iterator<T>
// Iterator pattern in TypeScript
interface Iterator<T> {
next(): T | null;
hasNext(): boolean;
reset(): void;
}
class ArrayIterator<T> implements Iterator<T> {
private collection: T[];
private index: number = 0;
constructor(collection: T[]) {
this.collection = collection;
}
next(): T | null {
if (this.hasNext()) {
return this.collection[this.index++];
}
return null;
}
hasNext(): boolean {
return this.index < this.collection.length;
}
reset(): void {
this.index = 0;
}
}
class CustomCollection<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getIterator(): Iterator<T> {
return new ArrayIterator(this.items);
}
}
// Usage
const collection = new CustomCollection<string>();
collection.add("A");
collection.add("B");
collection.add("C");
const iterator = collection.getIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}
// Fibonacci iterator
class FibonacciIterator implements Iterator<number> {
private current: number = 0;
private next: number = 1;
private limit: number;
private count: number = 0;
constructor(limit: number) {
this.limit = limit;
}
next(): number | null {
if (this.count >= this.limit) {
return null;
}
const value = this.current;
[this.current, this.next] = [this.next, this.current + this.next];
this.count++;
return value;
}
hasNext(): boolean {
return this.count < this.limit;
}
reset(): void {
this.current = 0;
this.next = 1;
this.count = 0;
}
}
const fibIterator = new FibonacciIterator(10);
while (fibIterator.hasNext()) {
console.log(fibIterator.next());
}
// Generic iterator with step
class StepIterator<T> implements Iterator<T> {
private collection: T[];
private index: number = 0;
private step: number;
constructor(collection: T[], step: number) {
this.collection = collection;
this.step = step;
}
next(): T | null {
if (this.hasNext()) {
const value = this.collection[this.index];
this.index += this.step;
return value;
}
return null;
}
hasNext(): boolean {
return this.index < this.collection.length;
}
reset(): void {
this.index = 0;
}
}Template Method for algorithm skeletons using TypeScript abstract classes.
- AbstractClass:
abstract class AbstractClass - Template method:
templateMethod(): string - ConcreteClass:
class ConcreteClass extends AbstractClass - Generic:
GenericTemplate<T, U>
// Template Method pattern in TypeScript
abstract class AbstractClass {
templateMethod(): string {
const results = [
this.step1(),
this.step2(),
this.step3()
];
return results.join(" -> ");
}
step1(): string {
return "Step 1";
}
abstract step2(): string;
step3(): string {
return "Step 3";
}
}
class ConcreteClass extends AbstractClass {
step2(): string {
return "Concrete Step 2";
}
}
// Usage
const concrete = new ConcreteClass();
console.log(concrete.templateMethod());
// Template method with hooks
abstract class DataProcessor {
process(data: any): any {
if (this.beforeProcess(data)) {
const result = this.transform(data);
this.afterProcess(result);
return result;
}
return null;
}
protected beforeProcess(data: any): boolean {
return true;
}
protected abstract transform(data: any): any;
protected afterProcess(data: any): void {
// Optional hook
}
}
class JSONProcessor extends DataProcessor {
protected transform(data: any): any {
return JSON.parse(data);
}
protected beforeProcess(data: any): boolean {
return typeof data === "string";
}
protected afterProcess(data: any): void {
console.log("JSON processed successfully");
}
}
class XMLProcessor extends DataProcessor {
protected transform(data: any): any {
// Simulate XML parsing
return { parsed: data };
}
}
const jsonProcessor = new JSONProcessor();
const xmlProcessor = new XMLProcessor();
console.log(jsonProcessor.process('{"name":"Alice"}'));
console.log(xmlProcessor.process("<user>Bob</user>"));
// Generic template method
abstract class GenericTemplate<T, U> {
templateMethod(input: T): U {
const validated = this.validate(input);
const processed = this.process(validated);
return this.format(processed);
}
protected abstract validate(input: T): T;
protected abstract process(input: T): U;
protected abstract format(input: U): U;
}
class StringTemplate extends GenericTemplate<string, string> {
protected validate(input: string): string {
return input.trim();
}
protected process(input: string): string {
return input.toUpperCase();
}
protected format(input: string): string {
return `[${input}]`;
}
}Builder pattern for constructing complex objects using TypeScript.
- Builder:
interface Builder - Director:
class Director - Generic builder:
class GenericBuilder<T> - Fluent interface: Method chaining
// Builder pattern in TypeScript
class Product {
private parts: string[] = [];
add(part: string): void {
this.parts.push(part);
}
listParts(): string {
return this.parts.join(", ");
}
}
interface Builder {
reset(): void;
buildStepA(): void;
buildStepB(): void;
getResult(): Product;
}
class ConcreteBuilder implements Builder {
private product: Product;
constructor() {
this.product = new Product();
}
reset(): void {
this.product = new Product();
}
buildStepA(): void {
this.product.add("Part A");
}
buildStepB(): void {
this.product.add("Part B");
}
getResult(): Product {
const result = this.product;
this.reset();
return result;
}
}
class Director {
private builder: Builder;
constructor(builder: Builder) {
this.builder = builder;
}
buildMinimal(): void {
this.builder.buildStepA();
}
buildFull(): void {
this.builder.buildStepA();
this.builder.buildStepB();
}
}
// Usage
const builder = new ConcreteBuilder();
const director = new Director(builder);
director.buildMinimal();
const product1 = builder.getResult();
console.log(product1.listParts()); // Part A
director.buildFull();
const product2 = builder.getResult();
console.log(product2.listParts()); // Part A, Part B
// Generic builder
class GenericBuilder<T> {
private target: Partial<T> = {};
set<K extends keyof T>(key: K, value: T[K]): this {
this.target[key] = value;
return this;
}
build(): T {
return this.target as T;
}
}
interface User {
name: string;
age: number;
email: string;
}
const userBuilder = new GenericBuilder<User>();
const user = userBuilder
.set("name", "Alice")
.set("age", 25)
.set("email", "alice@example.com")
.build();
console.log(user);
// Fluent builder with validation
class UserBuilder {
private user: Partial<User> = {};
name(name: string): this {
if (name.length < 2) {
throw new Error("Name must be at least 2 characters");
}
this.user.name = name;
return this;
}
age(age: number): this {
if (age < 0 || age > 150) {
throw new Error("Invalid age");
}
this.user.age = age;
return this;
}
email(email: string): this {
if (!email.includes("@")) {
throw new Error("Invalid email");
}
this.user.email = email;
return this;
}
build(): User {
if (!this.user.name || !this.user.email) {
throw new Error("Name and email are required");
}
return this.user as User;
}
}Prototype pattern for cloning objects using TypeScript.
- Prototype:
interface Prototype { clone(): Prototype; deepClone(): Prototype; } - ConcretePrototype:
class ConcretePrototype implements Prototype - Generic:
GenericPrototype<T> - Registry:
class PrototypeRegistry
// Prototype pattern in TypeScript
interface Prototype {
clone(): Prototype;
deepClone(): Prototype;
}
class ConcretePrototype implements Prototype {
private name: string;
private nested: { value: number };
constructor(name: string, nested: { value: number }) {
this.name = name;
this.nested = nested;
}
clone(): Prototype {
return new ConcretePrototype(this.name, this.nested);
}
deepClone(): Prototype {
return new ConcretePrototype(
this.name,
{ value: this.nested.value }
);
}
getName(): string {
return this.name;
}
getNestedValue(): number {
return this.nested.value;
}
setName(name: string): void {
this.name = name;
}
setNestedValue(value: number): void {
this.nested.value = value;
}
}
// Usage
const original = new ConcretePrototype("Original", { value: 42 });
const copy = original.clone() as ConcretePrototype;
copy.setName("Copy");
copy.setNestedValue(99);
console.log(original.getName()); // Original
console.log(original.getNestedValue()); // 42 (shallow copy)
const deepCopy = original.deepClone() as ConcretePrototype;
deepCopy.setNestedValue(100);
console.log(original.getNestedValue()); // 42 (deep copy)
// Generic prototype
class GenericPrototype<T> implements Prototype {
constructor(public data: T) {}
clone(): Prototype {
return new GenericPrototype(this.data);
}
deepClone(): Prototype {
return new GenericPrototype(JSON.parse(JSON.stringify(this.data)));
}
getData(): T {
return this.data;
}
setData(data: T): void {
this.data = data;
}
}
const genOriginal = new GenericPrototype({ name: "Alice", age: 25 });
const genCopy = genOriginal.clone() as GenericPrototype<{ name: string; age: number }>;
genCopy.setData({ name: "Bob", age: 30 });
console.log(genOriginal.getData()); // { name: "Alice", age: 25 }
console.log(genCopy.getData()); // { name: "Bob", age: 30 }
// Prototype registry
class PrototypeRegistry {
private prototypes: Map<string, Prototype> = new Map();
register(name: string, prototype: Prototype): void {
this.prototypes.set(name, prototype);
}
get(name: string): Prototype | null {
const prototype = this.prototypes.get(name);
return prototype ? prototype.clone() : null;
}
}
const registry = new PrototypeRegistry();
registry.register("user", new ConcretePrototype("User", { value: 1 }));
const userPrototype = registry.get("user");
console.log(userPrototype);Error handling using custom error classes, Result type, and Either pattern.
- Custom error:
class AppError extends Error - Result type:
type Result<T, E = Error> - Either pattern:
type Either<L, R> - Async result:
safeAsync<T>(fn: () => Promise<T>): Promise<Result<T>>
// Error Handling in TypeScript
// Custom error class
class AppError extends Error {
constructor(
public message: string,
public code: number,
public status: number = 400
) {
super(message);
this.name = "AppError";
}
}
// Result type
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function safeOperation<T>(fn: () => T): Result<T> {
try {
return { success: true, data: fn() };
} catch (error) {
return { success: false, error: error as Error };
}
}
// Usage
const result = safeOperation(() => {
if (Math.random() > 0.5) {
throw new Error("Random error");
}
return 42;
});
if (result.success) {
console.log("Data:", result.data);
} else {
console.log("Error:", result.error.message);
}
// Async result
async function safeAsync<T>(fn: () => Promise<T>): Promise<Result<T>> {
try {
const data = await fn();
return { success: true, data };
} catch (error) {
return { success: false, error: error as Error };
}
}
// Either type
type Either<L, R> =
| { kind: "left"; left: L }
| { kind: "right"; right: R };
function divide(a: number, b: number): Either<string, number> {
if (b === 0) {
return { kind: "left", left: "Division by zero" };
}
return { kind: "right", right: a / b };
}
const divisionResult = divide(10, 2);
if (divisionResult.kind === "right") {
console.log("Result:", divisionResult.right);
} else {
console.log("Error:", divisionResult.left);
}
// Try-catch with specific error types
function processUser(data: any): void {
try {
if (!data.name) {
throw new AppError("Name is required", 1001, 400);
}
if (!data.email) {
throw new AppError("Email is required", 1002, 400);
}
console.log("Processing user:", data);
} catch (error) {
if (error instanceof AppError) {
console.log(`App error [${error.code}]: ${error.message}`);
} else {
console.log("Unexpected error:", error);
}
}
}
processUser({ name: "Alice" });Serialization and deserialization using JSON with TypeScript interfaces.
- Serializable:
interface Serializable { toJSON(): any; fromJSON(data: any): void; } - Serializer:
class Serializer - Class method:
static fromJSON(data: any): User - Validation:
ValidatedSerializer
// Serialization and Deserialization in TypeScript
interface Serializable {
toJSON(): any;
fromJSON(data: any): void;
}
class User implements Serializable {
constructor(
public id: number,
public name: string,
public email: string,
public createdAt: Date = new Date()
) {}
toJSON(): any {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt.toISOString()
};
}
fromJSON(data: any): void {
this.id = data.id;
this.name = data.name;
this.email = data.email;
this.createdAt = new Date(data.createdAt);
}
static fromJSON(data: any): User {
const user = new User(data.id, data.name, data.email);
user.fromJSON(data);
return user;
}
}
// Serialization functions
function serialize<T>(obj: T): string {
return JSON.stringify(obj);
}
function deserialize<T>(json: string): T {
return JSON.parse(json);
}
// Usage
const user = new User(1, "Alice", "alice@example.com");
const serialized = serialize(user);
console.log(serialized);
const deserialized = deserialize<User>(serialized);
console.log(deserialized);
// Custom serialization with class
class Serializer {
static serialize<T>(obj: T): string {
if (obj && typeof obj === 'object' && 'toJSON' in obj) {
return JSON.stringify((obj as any).toJSON());
}
return JSON.stringify(obj);
}
static deserialize<T>(json: string, targetClass?: new (...args: any[]) => T): T {
const data = JSON.parse(json);
if (targetClass && 'fromJSON' in targetClass) {
return (targetClass as any).fromJSON(data);
}
return data;
}
}
// Using serializer
const user2 = new User(2, "Bob", "bob@example.com");
const json = Serializer.serialize(user2);
console.log(json);
const user3 = Serializer.deserialize(json, User);
console.log(user3);
// Serialization with validation
class ValidatedSerializer {
static serialize<T>(obj: T): string {
return JSON.stringify(obj);
}
static deserialize<T>(json: string, validator?: (data: any) => boolean): T | null {
try {
const data = JSON.parse(json);
if (validator && !validator(data)) {
throw new Error("Validation failed");
}
return data;
} catch (error) {
console.error("Deserialization error:", error);
return null;
}
}
}Type assertions and type casting using as and angle bracket syntax.
- as:
value as string - Angle bracket:
<string>value - Non-null:
value! - Double assertion:
value as any as string
// Type Assertions and Type Casting in TypeScript
// Type assertion with as
let someValue: any = "Hello TypeScript";
let strLength: number = (someValue as string).length;
console.log(strLength);
// Type assertion with angle bracket
let strLength2: number = (<string>someValue).length;
console.log(strLength2);
// Type assertion with unknown
let unknownValue: unknown = "Hello";
let stringValue: string = unknownValue as string;
// Type assertion for DOM elements
const element = document.getElementById("app") as HTMLDivElement;
// Type assertion for objects
interface User {
name: string;
age: number;
}
const data: any = { name: "Alice", age: 25 };
const user = data as User;
console.log(user.name);
// Non-null assertion operator
let maybeString: string | null = "Hello";
let definitelyString: string = maybeString!;
console.log(definitelyString);
// Double assertion
let value: any = "Hello";
let numberValue: number = value as any as number;
// Type assertion with generics
function assertType<T>(value: any): T {
return value as T;
}
const assertedUser = assertType<User>({ name: "Alice", age: 25 });
console.log(assertedUser.name);
// Type assertion vs type casting
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const animal: Animal = { name: "Rex" };
const dog = animal as Dog; // Type assertion
// dog.breed // Error: undefined
// Type guard with assertion
function isString(value: any): value is string {
return typeof value === "string";
}
function assertIsString(value: any): asserts value is string {
if (typeof value !== "string") {
throw new Error("Value is not a string");
}
}
function processValue(value: any): string {
assertIsString(value);
return value.toUpperCase();
}
console.log(processValue("hello")); // HELLOMixins for composition using TypeScript's class merging capabilities.
- applyMixins:
function applyMixins(derivedCtor: any, baseCtors: any[]) - Class mixins:
function Timestamped<TBase extends Constructor>(Base: TBase) - Functional mixins:
function mixin<T extends new (...args: any[]) => any>(...mixins: any[]) - Decorator-based:
@mixin(Disposable, Activatable)
// Mixins in TypeScript
// Mixin pattern
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
// Base classes
class Disposable {
isDisposed: boolean = false;
dispose(): void {
this.isDisposed = true;
console.log("Disposed");
}
}
class Activatable {
isActive: boolean = false;
activate(): void {
this.isActive = true;
console.log("Activated");
}
deactivate(): void {
this.isActive = false;
console.log("Deactivated");
}
}
// Class using mixins
class SmartObject implements Disposable, Activatable {
isDisposed: boolean = false;
isActive: boolean = false;
dispose: () => void;
activate: () => void;
deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable]);
// Usage
const smartObj = new SmartObject();
smartObj.activate();
smartObj.dispose();
// Alternative: Function mixins
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = new Date();
getTimestamp(): string {
return this.timestamp.toISOString();
}
};
}
function Versioned<TBase extends Constructor>(Base: TBase) {
return class extends Base {
version = 1;
incrementVersion(): void {
this.version++;
}
};
}
class BaseUser {
name: string;
constructor(name: string) {
this.name = name;
}
}
const TimestampedUser = Timestamped(BaseUser);
const VersionedTimestampedUser = Versioned(TimestampedUser);
const user = new VersionedTimestampedUser("Alice");
console.log(user.name);
console.log(user.getTimestamp());
console.log(user.version);
user.incrementVersion();
console.log(user.version);
// Functional mixins
function mixin<T extends new (...args: any[]) => any>(...mixins: any[]) {
return (target: T) => {
applyMixins(target, mixins);
return target;
};
}
// Using decorator-based mixins
@mixin(Disposable, Activatable)
class AnotherSmartObject implements Disposable, Activatable {
isDisposed: boolean = false;
isActive: boolean = false;
dispose: () => void;
activate: () => void;
deactivate: () => void;
}Type guards for runtime type checking using typeof, instanceof, and custom predicates.
- typeof:
typeof value === "string" - instanceof:
value instanceof Dog - Custom predicate:
function isString(value: any): value is string - Discriminated union:
kindproperty
// Type Guards in TypeScript
// typeof type guard
function isString(value: any): value is string {
return typeof value === "string";
}
function isNumber(value: any): value is number {
return typeof value === "number";
}
function isBoolean(value: any): value is boolean {
return typeof value === "boolean";
}
// instanceof type guard
class Animal {
name: string = "";
}
class Dog extends Animal {
breed: string = "";
}
class Cat extends Animal {
color: string = "";
}
function isDog(animal: Animal): animal is Dog {
return animal instanceof Dog;
}
// Custom type guard with predicate
interface User {
name: string;
email: string;
}
interface Admin {
name: string;
role: string;
permissions: string[];
}
function isAdmin(user: User | Admin): user is Admin {
return (user as Admin).role !== undefined;
}
// Discriminated union
interface Square {
kind: "square";
size: number;
}
interface Circle {
kind: "circle";
radius: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Square | Circle | Rectangle;
function isSquare(shape: Shape): shape is Square {
return shape.kind === "square";
}
function isCircle(shape: Shape): shape is Circle {
return shape.kind === "circle";
}
// Usage
function processValue(value: string | number): string {
if (isString(value)) {
return `String: ${value}`;
}
return `Number: ${value}`;
}
function handleAnimal(animal: Animal): string {
if (isDog(animal)) {
return `Dog: ${animal.name}, ${animal.breed}`;
}
return `Animal: ${animal.name}`;
}
function handleUser(user: User | Admin): string {
if (isAdmin(user)) {
return `Admin: ${user.name}, Role: ${user.role}`;
}
return `User: ${user.name}, Email: ${user.email}`;
}
function handleShape(shape: Shape): number {
if (isSquare(shape)) {
return shape.size * shape.size;
}
if (isCircle(shape)) {
return Math.PI * shape.radius * shape.radius;
}
return shape.width * shape.height;
}
// Type guard with array
function isArrayOfStrings(value: any[]): value is string[] {
return value.every(item => typeof item === "string");
}Type predicates for custom type narrowing using is keyword.
- Basic predicate:
value is string - Interface predicate:
value is HasName - Array predicate:
value is string[] - Class predicate:
value is Person
// Type Predicates in TypeScript
// Basic type predicate
function isString(value: any): value is string {
return typeof value === "string";
}
// Type predicate with interface
interface HasName {
name: string;
}
function hasName(value: any): value is HasName {
return value && typeof value.name === "string";
}
// Type predicate with union
type Status = "active" | "inactive" | "pending";
function isActiveStatus(status: string): status is "active" {
return status === "active";
}
// Type predicate with array
function isStringArray(value: any[]): value is string[] {
return value.every(item => typeof item === "string");
}
// Type predicate with object
function isUser(value: any): value is User {
return value &&
typeof value.name === "string" &&
typeof value.age === "number";
}
// Usage
function processValue(value: any): string {
if (isString(value)) {
return value.toUpperCase();
}
return "Not a string";
}
function processUser(value: any): string {
if (isUser(value)) {
return `User: ${value.name}, Age: ${value.age}`;
}
return "Not a user";
}
function processStatus(status: string): string {
if (isActiveStatus(status)) {
return "Status is active";
}
return "Status is not active";
}
// Type predicate in filter
const mixedArray: any[] = ["hello", 42, "world", true, "typescript"];
const stringsOnly = mixedArray.filter(isString);
console.log(stringsOnly); // ["hello", "world", "typescript"]
// Type predicate with class
class Person {
constructor(public name: string, public age: number) {}
}
function isPerson(value: any): value is Person {
return value instanceof Person;
}
const person = new Person("Alice", 25);
if (isPerson(person)) {
console.log(person.name);
}
// Type predicate with generic
function isType<T>(value: any, constructor: new (...args: any[]) => T): value is T {
return value instanceof constructor;
}
const date = new Date();
if (isType(date, Date)) {
console.log(date.getFullYear());
}Advanced TypeScript types including conditional, mapped, and template literal types.
- Conditional:
T extends U ? X : Y - Mapped:
{ [P in keyof T]: T[P] } - Template literal:
Hello, ${string} - Recursive:
type DeepReadonly<T>
// Advanced Types in TypeScript
// Conditional types
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<number>; // false
// Infer keyword
type ElementType<T> = T extends (infer U)[] ? U : never;
type C = ElementType<string[]>; // string
type D = ElementType<number>; // never
// Mapped types
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Template literal types
type Greeting = `Hello, ${string}`;
type Color = "red" | "green" | "blue";
type ColorMessage = `Color: ${Color}`;
// Recursive types
type JSONValue = string | number | boolean | null | JSONObject | JSONArray;
interface JSONObject {
[key: string]: JSONValue;
}
type JSONArray = JSONValue[];
// Omit type
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Exclude and Extract
type Status = "active" | "inactive" | "pending";
type Active = Exclude<Status, "inactive" | "pending">; // "active"
type Pending = Extract<Status, "pending" | "active">; // "pending" | "active"
// NonNullable
type NonNullable<T> = T extends null | undefined ? never : T;
// ReturnType
function getUser() {
return { name: "Alice", age: 25 };
}
type UserType = ReturnType<typeof getUser>; // { name: string; age: number }
// Parameters
function greet(name: string, age: number): string {
return `Hello ${name}, age ${age}`;
}
type GreetParams = Parameters<typeof greet>; // [string, number]
// Usage
interface User {
id: number;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type UserName = Pick<User, "name" | "email">;
type UserWithoutId = Omit<User, "id">;Type-safe DOM manipulation using TypeScript with HTML elements.
- Element selection:
document.getElementById("id") as HTMLDivElement - Event handling:
addEventListener("click", (event: MouseEvent) => { }) - Generic functions:
function getElement<T extends HTMLElement>(id: string): T - Type-safe creation:
createCustomElement<T>(tagName: string, options: Partial<T>): T
// TypeScript with DOM Manipulation
// Getting elements
const element = document.getElementById("app") as HTMLDivElement;
const elements = document.querySelectorAll(".item") as NodeListOf<HTMLElement>;
const button = document.querySelector<HTMLButtonElement>("#submit")!;
// Creating elements
const newDiv = document.createElement("div");
newDiv.className = "container";
newDiv.innerHTML = "<p>Hello World</p>";
document.body.appendChild(newDiv);
// Event handling
element.addEventListener("click", (event: MouseEvent) => {
console.log("Clicked at", event.clientX, event.clientY);
});
// Typed event handlers
function handleInput(event: Event): void {
const input = event.target as HTMLInputElement;
console.log("Input value:", input.value);
}
const input = document.querySelector<HTMLInputElement>("#input");
input?.addEventListener("input", handleInput);
// Form handling
interface FormData {
name: string;
email: string;
age: number;
}
function handleFormSubmit(event: SubmitEvent): void {
event.preventDefault();
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
const data: FormData = {
name: formData.get("name") as string,
email: formData.get("email") as string,
age: Number(formData.get("age"))
};
console.log("Form data:", data);
}
const form = document.querySelector<HTMLFormElement>("#form");
form?.addEventListener("submit", handleFormSubmit);
// Fetch with TypeScript
interface UserData {
id: number;
name: string;
email: string;
}
async function fetchUser(id: number): Promise<UserData> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json() as UserData;
}
// DOM manipulation with generics
function getElement<T extends HTMLElement>(id: string): T {
const element = document.getElementById(id);
if (!element) {
throw new Error(`Element with id ${id} not found`);
}
return element as T;
}
// Type-safe class list
function toggleClass(element: HTMLElement, className: string): void {
element.classList.toggle(className);
}
// Custom element creation
function createCustomElement<T extends HTMLElement>(
tagName: string,
options: Partial<T>
): T {
const element = document.createElement(tagName) as T;
Object.assign(element, options);
return element;
}Advanced generics including constraints, keyof, conditional types, and recursive types.
- Constraints:
<T extends object> - keyof:
<T, K extends keyof T> - Conditional:
type Flatten<T> = T extends any[] ? T[number] : T - Recursive:
type DeepReadonly<T>
// Advanced Generics in TypeScript
// Generic constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Generic with multiple constraints
function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
// Generic with default type
function createArray<T = string>(length: number, value: T): T[] {
return Array(length).fill(value);
}
// Generic with keyof
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key]);
}
// Generic with conditional types
type Flatten<T> = T extends any[] ? T[number] : T;
// Generic with recursive types
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// Generic with tuple types
type FirstElement<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type LastElement<T extends any[]> = T extends [...any[], infer L] ? L : never;
// Generic with function types
type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never;
// Usage
interface User {
id: number;
name: string;
age: number;
}
const user = { id: 1, name: "Alice", age: 25 };
console.log(getProperty(user, "name"));
const merged = mergeObjects({ name: "Alice" }, { age: 25 });
console.log(merged);
const stringArray = createArray(3, "Hello");
const numberArray = createArray<number>(3, 42);
const users: User[] = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 }
];
const names = pluck(users, "name");
console.log(names);
type UserType = Flatten<User[]>; // User
type DeepReadonlyUser = DeepReadonly<{
id: number;
name: string;
address: {
city: string;
zip: string;
};
}>;
type First = FirstElement<[1, 2, 3]>; // 1
type Last = LastElement<[1, 2, 3]>; // 3
type Params = Parameters<(name: string, age: number) => string>; // [string, number]
type Return = ReturnType<(name: string, age: number) => string>; // stringBest practices for writing clean, type-safe TypeScript code.
- Explicit types: Use for function parameters and returns
- Interfaces: Use for object shapes
- Type guards: Use for type narrowing
- readonly: Use for immutable properties
- Utility types: Partial, Readonly, Pick, Omit
// TypeScript Best Practices
// 1. Use explicit types for function parameters and returns
function add(a: number, b: number): number {
return a + b;
}
// 2. Use interfaces for object shapes
interface User {
id: number;
name: string;
email: string;
}
// 3. Use type guards for type narrowing
function isUser(value: any): value is User {
return value && typeof value.name === "string";
}
// 4. Use readonly for immutable properties
interface ReadonlyUser {
readonly id: number;
name: string;
}
// 5. Use optional properties for optional fields
interface PartialUser {
name?: string;
email?: string;
}
// 6. Use union types for multiple possible types
type Status = "active" | "inactive" | "pending";
// 7. Use generics for reusable code
function identity<T>(value: T): T {
return value;
}
// 8. Use type assertions sparingly
const element = document.getElementById("app") as HTMLDivElement;
// 9. Use enum for constants
enum HttpStatus {
OK = 200,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404
}
// 10. Use strict mode in tsconfig
// "strict": true
// 11. Use interfaces for function types
interface GreetFunction {
(name: string): string;
}
// 12. Use index signatures for dynamic objects
interface StringMap {
[key: string]: string;
}
// 13. Use utility types
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type PickUser = Pick<User, "name" | "email">;
// 14. Use never for unreachable code
function throwError(message: string): never {
throw new Error(message);
}
// 15. Use void for functions with no return
function log(message: string): void {
console.log(message);
}
// 16. Use unknown for uncertain types
let uncertain: unknown = "Hello";
if (typeof uncertain === "string") {
console.log(uncertain.toUpperCase());
}
// 17. Use async/await for promises
async function fetchData(): Promise<User> {
const response = await fetch("/api/user");
return response.json();
}
// 18. Use const assertions
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
} as const;
// 19. Use non-null assertion only when sure
const button = document.getElementById("submit")!;
// 20. Use type imports for better performance
// import type { User } from "./types";