TypeScript


πŸ§‘β€πŸŽ“ Beginner Level

βœ… 1. TypeScript Basics

  • What is TypeScript? Why use it?
  • Installing TypeScript (tsc, tsconfig.json)
  • Compilation: .ts to .js

βœ… 2. Basic Types

  • string, number, boolean
  • any, unknown, void, null, undefined
  • let, const, readonly

βœ… 3. Arrays & Tuples

  • number[], Array<string>
  • Tuples: [string, number]

βœ… 4. Enums & Literals

  • enum Direction { Up, Down }
  • Literal types: "success" | "error"

βœ… 5. Functions

  • Function return type & parameters
  • Optional/default parameters
  • Function types & signatures

πŸ”§ Intermediate Level

βœ… 6. Type Aliases & Interfaces

  • type User = {...}
  • interface User {...}
  • Differences & when to use

βœ… 7. Union & Intersection Types

  • type A = string | number
  • type B = A & { extra: boolean }

βœ… 8. Type Assertions & Guards

  • as keyword
  • typeof, instanceof, custom type guards

βœ… 9. Classes & OOP

  • public, private, protected
  • Constructors, Inheritance
  • implements, abstract, static

βœ… 10. Modules

  • import, export, default vs named
  • Working with esModuleInterop, allowSyntheticDefaultImports

🧠 Advanced Level

βœ… 11. Generics

  • Generic functions: <T>(arg: T) => T
  • Generic classes & interfaces
  • Constraints: <T extends Lengthwise>
  • Default type params

βœ… 12. Utility Types

  • Partial<T>, Required<T>, Readonly<T>
  • Pick<T, K>, Omit<T, K>
  • Record<K, T>, Exclude, Extract, NonNullable

βœ… 13. Type Manipulation

  • Conditional Types: T extends U ? X : Y
  • Mapped Types: { [K in keyof T]: T[K] }
  • Template Literal Types
  • Key Remapping

βœ… 14. Declaration Merging

  • Interface merging
  • Module augmentation

βœ… 15. Advanced Types

  • keyof, typeof, infer
  • ReturnType<T>, Parameters<T>, ConstructorParameters<T>

🧰 Tooling & Ecosystem

βœ… 16. tsconfig.json Deep Dive

  • strict, noImplicitAny, strictNullChecks
  • paths, baseUrl, module, target, lib, esModuleInterop

βœ… 17. Working with 3rd Party Libs

  • DefinitelyTyped: @types
  • Writing Custom Declaration Files (*.d.ts)
  • Modules without types

πŸ§ͺ Testing, Linting, Best Practices

βœ… 18. Linting & Formatting

  • ESLint with TypeScript
  • Prettier Setup

βœ… 19. Type-Safe APIs & Error Handling

  • API responses & generics
  • Discriminated unions for state handling

πŸš€ Projects & Real-World Use Cases

  • Build type-safe REST API client (with Axios)
  • React app with TypeScript (Props, State, Hooks)
  • Form validation with Zod + TS
  • Node.js + TypeScript + Express backend
  • Mono repo (TurboRepo, Nx)

Here’s a detailed, WordPress-friendly blog post on:


🟦 What is TypeScript? When and Where Should You Use It? (With Version History)


πŸ“Œ Introduction

TypeScript is a strongly typed, object-oriented, compiled superset of JavaScript. It’s developed and maintained by Microsoft and designed to overcome limitations of JavaScript in large-scale applications.

TypeScript = JavaScript + Static Types

It compiles into standard JavaScript so it can run anywhere JavaScript runs – in the browser, on Node.js, or any JavaScript engine.


πŸš€ Why TypeScript?

FeatureJavaScriptTypeScript
Static Typing❌ Noβœ… Yes
Compile-Time Checks❌ Noβœ… Yes
Modern Syntaxβœ… Partialβœ… Full
IDE Support😐 LimitedπŸš€ Great
Tooling❌ Weakβœ… Strong

🎯 Where to Use TypeScript?

βœ… Ideal Use Cases:

  • Large codebases with multiple developers
  • Enterprise or production applications
  • Node.js backend applications
  • Angular, React, Vue projects (TypeScript is default in Angular!)
  • Modern SaaS and e-commerce platforms
  • Open-source projects and libraries

🚫 When Not Needed:

  • Small scripts or throwaway code
  • Quick one-time automation tasks
  • Learning basic JS fundamentals

πŸ› οΈ Key Features of TypeScript

  • Optional Static Typing
  • Type Inference
  • Interfaces and Classes
  • Enums and Tuples
  • Decorators
  • Access Modifiers (public/private/protected)
  • Advanced IntelliSense
  • Better refactoring and error detection

🧭 TypeScript Version History (Highlights)

VersionRelease DateMajor Features
0.8Oct 2012First public release
1.0Apr 2014First stable release
2.0Sep 2016readonly, never, non-nullable types
3.0Jul 2018Tuples with optional/rest elements, project references
4.0Aug 2020Variadic tuple types, labeled tuple elements
4.1Nov 2020Template literal types
4.4Aug 2021Control flow analysis of aliased conditions
4.9Nov 2022satisfies operator
5.0Mar 2023Decorators, const type params, improved speed
5.4Feb 2024Improved type narrowing, type predicates
5.5Jul 2024Inferred type aliases, better tooling, faster compilation


πŸ“¦ Installation

npm install -g typescript

Create a config file:

npx tsc --init


πŸ§‘β€πŸ’» Example

function greet(name: string): string {
  return `Hello, ${name}`;
}

console.log(greet("Hari")); // βœ… OK
console.log(greet(42));     // ❌ Compile-time error




πŸš€ TypeScript Setup for Beginners – A Complete Guide

Summary: In this post, you’ll learn how to set up your TypeScript development environment from scratch using Node.js, TypeScript compiler, and VS Code. Perfect for beginners starting their TypeScript journey!


πŸ“¦ Tools You’ll Need

Before writing TypeScript code, let’s set up the right tools:

  1. Node.js – Runtime to install and use the TypeScript compiler.
  2. TypeScript Compiler (tsc) – Converts .ts files into .js.
  3. Visual Studio Code (VS Code) – A lightweight, powerful editor with great TypeScript support.

πŸ’‘ Bonus Tool:
Install the Live Server extension in VS Code for auto-reload and easy testing in the browser.


πŸ›  Step 1: Install Node.js

Follow these steps to install Node.js:

  • Go to the Node.js official download page
  • Choose the version for your OS (Windows, macOS, or Linux)
  • Run the installer and follow the setup wizard
  • After installation, open your terminal and check the version:
node -v


πŸ”§ Step 2: Install the TypeScript Compiler

Use npm (comes with Node.js) to install TypeScript globally:

npm install -g typescript

Verify installation:

tsc --version

Output:

Version 5.5.3

⚠️ On Windows, if tsc is not recognized, add this path to your system’s environment variable:

C:\Users\<YourName>\AppData\Roaming\npm


⚑ Bonus: Run TypeScript Without Compiling (tsx)

Want to run TypeScript directly without using tsc each time?

Install tsx globally:

npm install -g tsx

Now you can run TypeScript files instantly:

tsx index.ts


πŸ’» Step 3: Install VS Code (Recommended)

To install Visual Studio Code:

  • Visit the VS Code download page
  • Choose your OS and download the installer
  • Run the setup wizard
  • Launch VS Code after install

πŸ‘‰ Install the Live Server extension:

  • Go to Extensions (Ctrl + Shift + X)
  • Search β€œLive Server”
  • Click Install

🧰 Step 4: Initialize TypeScript Project

In your project folder, run:

npx tsc --init

It generates a tsconfig.json file with default settings:

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Optional: Customize your config to organize files:

{
  "include": ["src"],
  "compilerOptions": {
    "outDir": "dist"
  }
}


πŸ” Summary

TypeScript compiles .ts to .js

Use tsc to compile or tsx to run instantly

Use VS Code + Live Server for the best experience

Use tsconfig.json to control compilation



🧠 TypeScript AST Explorer – Understand Your Code Internals

Summary: The TypeScript AST Explorer lets you peek under the hood of your TypeScript code and see how the compiler interprets it using the Abstract Syntax Tree (AST). It’s a great tool for learning, debugging, and building code tools.


πŸ” What is an AST?

An Abstract Syntax Tree (AST) is a tree-like structure that represents the syntax of your code. The TypeScript compiler uses it to understand your code before type-checking and emitting JavaScript.


πŸ’‘ Why Use AST Explorer?

  1. πŸ§ͺ Inspect how TypeScript parses your code
  2. πŸ›  Debug type errors at a deeper level
  3. πŸ“¦ Learn how custom tools (like ESLint, Babel) analyze code
  4. 🧰 Useful for writing linters, code transformers, or compilers

🌐 Try it Live

Explore AST for any TypeScript code at:

πŸ‘‰ AST Viewer: https://ts-ast-viewer.com


πŸ§ͺ Example

Try this code in the AST Viewer:

function greet(name: string): string {
  return `Hello, ${name}`;
}

You’ll see:

  • FunctionDeclaration
  • Parameter
  • StringLiteral
  • ReturnStatement

Each node shows how the compiler breaks down your code.


πŸ“š Summary

ToolPurpose
AST ExplorerVisualizes TypeScript AST
Use CasesDebugging, tool-building, code insight
Try It Herets-ast-viewer.com



🧠 TypeScript Data Types: Deep Dive into Primitive and Reference Types

Whether you’re just starting with TypeScript or brushing up your knowledge, understanding data types is the key to writing reliable, scalable, and type-safe code. Let’s explore every data type in TypeScript, one by one β€” with examples.


βœ… Part 1: Primitive Data Types

Primitive types hold simple and immutable values and are stored directly in memory.


1. string

Represents textual data.

let username: string = "Hari Mohan";
let greeting: string = `Hello, ${username}`;


2. number

Represents both integers and floating-point numbers.

let age: number = 30;
let price: number = 199.99;
let hex: number = 0xff;


3. boolean

Represents true or false values.

let isLoggedIn: boolean = true;
let hasPermission: boolean = false;


4. null

Represents an intentional absence of value.

let user: null = null;

Note: Must use "strictNullChecks": true in tsconfig.json to handle null properly.


5. undefined

Means the variable has been declared but not assigned a value.

let data: undefined = undefined;


6. bigint

Used for very large integers beyond Number.MAX_SAFE_INTEGER.

let bigNumber: bigint = 9007199254740991n;


7. symbol

Used to create unique identifiers, useful for object keys.

const id: symbol = Symbol("id");


πŸ” Recap: Primitive Type Table

TypeExample
string"Hari"
number42, 3.14
booleantrue, false
nullnull
undefinedundefined
bigint123456789n
symbolSymbol("key")

βœ… Part 2: Reference Data Types

Reference types store memory addresses, not actual values. Any changes made to one variable affect the other.


1. object

Can hold key-value pairs, complex structured data.

let user: { name: string; age: number } = {
  name: "Hari",
  age: 28
};


2. array

Used to store ordered collections.

let scores: number[] = [100, 90, 95];
let fruits: Array<string> = ["apple", "banana"];


3. tuple

A fixed-length array where each element has a specific type.

let person: [string, number] = ["Hari", 30];


4. function

Functions are first-class objects and can be typed.

function add(a: number, b: number): number {
  return a + b;
}

You can also assign a function to a variable:

const greet: (name: string) => string = (name) => `Hello, ${name}`;


5. class

Blueprint for creating objects (OOP style).

class Car {
  constructor(public brand: string) {}
}

let myCar = new Car("Tesla");


6. interface

Defines a structure for objects.

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

let admin: User = { name: "Mohan", age: 25 };


7. enum

Defines a set of named constants.

enum Direction {
  Up,
  Down,
  Left,
  Right
}

let move: Direction = Direction.Up;


8. any

A type that disables type-checking. Use sparingly.

let data: any = "Hello";
data = 123;
data = true;


9. unknown

Like any, but safer β€” forces type checking before use.

let value: unknown = "test";

if (typeof value === "string") {
  console.log(value.toUpperCase());
}


10. never

Represents a value that never occurs (e.g., a function that throws or never ends).

function throwError(): never {
  throw new Error("Something went wrong");
}


11. void

Represents the absence of a return value, often used in functions.

function logMessage(): void {
  console.log("Logging...");
}


πŸ” Recap: Reference Types Summary

TypeExample
object{ name: "Hari" }
array[1, 2, 3]
tuple["Hari", 30]
function(a: number, b: number) => {}
classclass Car {}
interfaceinterface User {}
enumenum Direction {}
anyany
unknownunknown
voidvoid
nevernever

🧠 Final Words

Understanding the difference between primitive and reference types helps you:

  • Avoid bugs caused by shared references.
  • Leverage TypeScript’s type system for safer code.
  • Improve scalability and maintainability.




βœ… Primitive Types

These are simple, immutable values:

  • string β€” "Hello"
  • number β€” 42, 3.14
  • boolean β€” true, false
  • null, undefined
  • bigint, symbol

🧱 Reference/Object Types

Used for structured data and reusable logic:

  • object β€” { name: "Hari" }
  • array β€” [1, 2, 3]
  • tuple β€” ["Hari", 30]
  • enum, function, class, interface

πŸ” Advanced Types

Take your type safety to the next level:

  • union types β€” string | number
  • intersection types β€” Admin & User
  • literal types β€” "GET" | "POST"
  • mapped types β€” Partial<T>, Readonly<T>

βœ… Best Practices

  • Use let & const (not var)
  • Avoid any (prefer unknown or strict types)
  • Leverage Type Inference and Utility Types


πŸ“¦ 3. Arrays & Tuples in TypeScript

In TypeScript, arrays and tuples allow you to work with lists of values β€” with added type safety and structure.


πŸ”Ή Arrays

You can define arrays in two ways:

βœ… Syntax 1: number[]

let numbers: number[] = [1, 2, 3, 4];

βœ… Syntax 2: Array<string>

let fruits: Array<string> = ["apple", "banana", "mango"];

You get autocomplete and type safety for each item in the array.


πŸ”Ή Tuples

Tuples are arrays with fixed length and types in a specific order.

let user: [string, number] = ["Hari", 25];

  • user[0] is always a string
  • user[1] is always a number

Trying to reverse the types or add extra values will raise a compile-time error.


🧠 Why Use Tuples?

Perfect for returning multiple values of known types from a function:

function getUser(): [string, number] {
  return ["Mohan", 30];
}


πŸ“Œ Arrays are flexible lists of items of the same type.
πŸ“Œ Tuples are structured containers for a fixed set of typed values.



Mastering Arrays & Tuples in TypeScript – A Complete Guide with All Operations

TypeScript offers powerful tools for working with collections of data. Two of the most foundational structures you’ll use are arrays and tuples. In this post, we’ll explore the differences between them, how to declare and manipulate them, and walk through all the essential operations.


πŸ”Ή What Are Arrays in TypeScript?

An array is a collection of elements that are of the same type.

βœ… Declaring Arrays

let scores: number[] = [95, 88, 76];
let names: Array<string> = ["Hari", "Mohan"];

Both number[] and Array<string> are valid syntax.


πŸ”Έ Array Operations

Here’s a list of all common array operations you should know:

1. πŸ“₯ push() – Add to end

scores.push(100);

2. πŸ“€ pop() – Remove from end

let last = scores.pop();

3. πŸšͺ shift() – Remove from start

let first = scores.shift();

4. πŸšͺ unshift() – Add to start

scores.unshift(90);

5. πŸ”„ map() – Transform array

let doubled = scores.map(s => s * 2);

6. 🎯 filter() – Filter elements

let above80 = scores.filter(s => s > 80);

7. πŸ” find() – Find first match

let firstHigh = scores.find(s => s > 90);

8. πŸ“ indexOf() – Find index

let idx = names.indexOf("Hari");

9. πŸ”’ slice() – Get sub-array

let topTwo = scores.slice(0, 2);

10. βœ‚οΈ splice() – Insert/remove at index

scores.splice(1, 1); // remove 1 item at index 1
scores.splice(1, 0, 89); // insert 89 at index 1

11. πŸ”— concat() – Merge arrays

let all = scores.concat([60, 70]);

12. πŸ” forEach() – Iterate

scores.forEach(s => console.log(s));

13. πŸ”€ join() – Join into string

let str = scores.join(", ");

14. πŸ” reduce() – Accumulate values

let total = scores.reduce((sum, s) => sum + s, 0);


πŸ§ͺ Array Type Checking

TypeScript ensures type safety:

let marks: number[] = [90, 80];
// marks.push("A"); ❌ Error


πŸ”Ή Tuples in TypeScript

A tuple is a fixed-size array with known types for each element.

βœ… Basic Tuple Example

let user: [string, number] = ["Hari", 25];

Each index has a fixed type.


πŸ”Έ Tuple Features

1. πŸ†— Fixed Length

let coordinates: [number, number] = [10, 20];
// coordinates = [10]; ❌ Error

2. 🧩 Multiple Types

let data: [string, boolean, number] = ["OK", true, 1];

3. πŸ†“ Optional Elements

let result: [string, number?] = ["Success"];

4. πŸ“₯ Destructuring

const [name, age] = user;

5. 🧠 Named Labels (TS 4.0+)

type Point = [x: number, y: number];
const origin: Point = [0, 0];

6. βž• Tuple with Rest Elements

let log: [string, ...string[]] = ["info", "msg1", "msg2"];


πŸ”„ Tuple Operations (with caution)

Tuples do allow some array methods, but you must be careful:

let t: [string, number] = ["A", 1];

t.push(2); // Valid, but breaks strict type definition
t.pop();   // Works

To prevent this, enable strictTupleTypes in tsconfig.json.


🧠 When to Use Arrays vs Tuples?

FeatureArrayTuple
SizeDynamicFixed
TypeSame for all elementsCan vary per index
Use CaseLists, collectionsStruct-like data (e.g., [name, age])
AccessIndexed genericallyIndexed semantically

🧰 Real-World Use Cases

βœ… Array Use Cases

  • List of tasks: Task[]
  • Product catalog: Product[]
  • Chart data: number[]

βœ… Tuple Use Cases

  • Return multiple values from a function
function getUser(): [string, number] {
  return ["Hari", 30];
}

  • Coordinate points: [x: number, y: number]
  • RGB color values: [number, number, number]

πŸ”š Conclusion

  • Arrays are perfect for working with dynamic collections of the same type.
  • Tuples provide structured, positional data types useful in specific patterns.
  • Mastering both helps you write expressive, type-safe code.


πŸ”Ή The Ultimate Guide to Tuples in TypeScript: Definition, Methods & Best Practices

When building reliable TypeScript applications, understanding tuples is a powerful step toward writing cleaner, safer code. They look like arrays but behave quite differently.

In this article, you’ll learn:

  • βœ… What tuples are
  • πŸ” How they differ from arrays
  • πŸ›  Tuple operations and methods
  • 🧠 Best practices and use cases

🧾 What Is a Tuple in TypeScript?

A tuple is a fixed-length, typed array where the types and order of elements are known ahead of time.

βœ… Basic Syntax

let person: [string, number] = ["Hari", 30];

  • person[0] is always a string
  • person[1] is always a number
  • Changing the type or order will throw an error

πŸ” Tuple vs Array: What’s the Difference?

FeatureTupleArray
LengthFixedDynamic
TypesCan vary per elementAll elements must be same type
Use CaseStructured data (name, age)Lists of same-type items
Type SafetyEnforced per indexUniform across all items

πŸ§ͺ Declaring Tuples

let userInfo: [string, boolean, number] = ["Hari", true, 25];

Optional Elements

let point: [number, number?] = [10];

Rest Elements (TS 4.0+)

let rgb: [number, ...number[]] = [255, 0, 0, 128];

Labeled Tuples (TS 4.0+)

type Response = [status: string, code: number];
const res: Response = ["OK", 200];


πŸ”§ Tuple Methods and Operations

Tuples in TypeScript inherit all JavaScript array methods but should be used carefully to avoid violating tuple type constraints.

βœ… Access by Index

const user: [string, number] = ["Hari", 30];
console.log(user[0]); // "Hari"
console.log(user[1]); // 30

βœ… Destructuring

const [name, age] = user;


πŸ›  Array Methods on Tuples

MethodDescriptionUse Carefully?
push()Adds to the end of tuple (may violate length)βœ… / ⚠️
pop()Removes the last elementβœ… / ⚠️
unshift()Adds to beginningβœ… / ⚠️
shift()Removes first elementβœ… / ⚠️
includes()Checks for existenceβœ…
indexOf()Gets the index of a valueβœ…
map()Maps over values (requires uniform type)⚠️
forEach()Iterates over each itemβœ…
slice()Extracts part of the tuple⚠️
splice()Inserts/removes values (can break types)⚠️
join()Converts to stringβœ…

Example: push() (⚠️ Dangerous!)

let user: [string, number] = ["Hari", 25];
user.push("Extra"); // No TypeScript error (JS allows), but breaks type structure

πŸ”’ Use strictTupleTypes in tsconfig.json to prevent unsafe mutations.


πŸ’‘ Best Practices for Tuples

  1. βœ… Use when the structure is known and limited
  2. ❌ Don’t treat tuples like generic arrays
  3. βœ… Use readonly when you want immutable tuples
  4. βœ… Use labeled tuples for clarity and autocomplete

πŸ“Œ Real-World Use Cases

Function Return Values

function getUser(): [string, number] {
  return ["Hari", 25];
}

Coordinate System

type Point = [x: number, y: number];
const origin: Point = [0, 0];

Key-Value Pairs

const entry: [string, string] = ["username", "hari123"];

API Response Metadata

type ApiResponse = [status: string, success: boolean];
const response: ApiResponse = ["Created", true];


🧠 Summary

  • βœ… Tuples allow multiple known types in a fixed order
  • πŸ§ͺ They support most array methods β€” but use cautiously
  • πŸ›  They’re great for structured data like [name, age], [x, y], or [status, code]
  • πŸ”’ Enabling strictTupleTypes ensures safety from invalid operations



πŸ“¦ All Array & Tuple Methods in TypeScript β€” Complete Cheat Sheet with Examples

Arrays and Tuples are two of the most commonly used data structures in TypeScript. This guide gives you a complete list of methods, their purpose, and how safely they apply to arrays and tuples.


πŸ”Ή What’s the Difference?

FeatureArrayTuple
LengthDynamicFixed
TypesAll elements have same typeEach element can have different type
MethodsFully supportedSupported but can break safety

🧰 Array & Tuple Methods – Categorized

πŸ” 1. Iteration & Transformation

MethodDescriptionWorks on Tuple?Example
forEach()Iterate over each elementβœ…arr.forEach(v => ...)
map()Transform each element⚠️ Use carefullyarr.map(v => v * 2)
filter()Filter items based on condition⚠️ Loses tuple typesarr.filter(v => v > 0)
reduce()Reduce to a single valueβœ…arr.reduce(...)
every()Check if all items match conditionβœ…arr.every(v => v > 0)
some()Check if at least one item matchesβœ…arr.some(v => v < 0)

πŸ”„ 2. Mutation Methods

MethodDescriptionWorks on Tuple?Example
push()Add item to end⚠️ Unsafearr.push(99)
pop()Remove last item⚠️ Unsafearr.pop()
shift()Remove first item⚠️ Unsafearr.shift()
unshift()Add item at start⚠️ Unsafearr.unshift(1)
splice()Add/remove at specific index⚠️ Unsafearr.splice(1, 1)
fill()Replace all values⚠️ Dangerousarr.fill(0)
copyWithin()Copy elements to another index⚠️ Dangerousarr.copyWithin(0, 1)
reverse()Reverse in place⚠️ Dangerousarr.reverse()
sort()Sort in place⚠️ Dangerousarr.sort()

πŸ”’ Tuples are meant to be fixed-size. Mutation methods should be avoided unless using rest elements. Use strictTupleTypes in tsconfig.json.


πŸ§ͺ 3. Search & Check Methods

MethodDescriptionWorks on Tuple?Example
includes()Check if value existsβœ…arr.includes("x")
indexOf()Find index of valueβœ…arr.indexOf(10)
lastIndexOf()Last index of valueβœ…arr.lastIndexOf("a")
find()Get first matchβœ…arr.find(v => v > 10)
findIndex()Index of first matchβœ…arr.findIndex(v => v < 0)

πŸ“¦ 4. Extraction & Conversion

MethodDescriptionWorks on Tuple?Example
slice()Return a shallow copy⚠️ Tuple becomes arrayarr.slice(0, 2)
concat()Merge arrays⚠️ Unsafearr.concat(other)
join()Join all elements into a stringβœ…arr.join(", ")
toString()Convert to stringβœ…arr.toString()
flat()Flatten nested arraysβœ…[[1], [2]].flat()

🧠 Special Tuple Features

FeatureDescriptionExample
Labeled TuplesLabel positions in tuple for claritytype Point = [x: number, y: number]
Optional ElementsAllow missing elements in tuplelet t: [number, number?] = [10];
Rest TuplesVariable length from specific positionlet log: [string, ...string[]]

πŸ“Œ Best Practices for Tuples

  1. βœ… Use for fixed, known structures ([name, age])
  2. ❌ Avoid unsafe mutations (push, pop) on plain tuples
  3. βœ… Enable strictTupleTypes for safer code
  4. βœ… Use readonly to make tuples immutable
  5. βœ… Use labeled tuples to improve readability

πŸ”š Conclusion

  • βœ… Arrays are flexible and support all JS methods
  • βœ… Tuples are strict, typed, and safe when fixed
  • 🧠 Most array methods work on tuples but should be used cautiously
  • ✨ Use the right structure based on your use case: array for lists, tuple for structured pairs/groups



Deep Dive into Enums and Literal Types in TypeScript

TypeScript enhances JavaScript with static typing, and two of its most powerful tools for defining fixed sets of values are Enums and Literal Types. Both enforce constraints at the type level, improving code clarity, autocomplete, and reducing bugs.

In this post, we’ll take a deep dive into:

  • What are Enums & Literal Types?
  • Their types, syntax, behavior
  • Internal JavaScript output
  • Advanced use cases
  • Performance and memory
  • Best practices and anti-patterns


1. Enums: Introduction & Purpose

Enums (short for “enumerations”) are a feature in TypeScript that lets you define a set of named constants β€” ideal for representing categories, options, or flags.

Why use enums?

  • Group related constants
  • Improve type safety
  • Enable auto-completion
  • Enhance readability over raw strings/numbers

2. Numeric Enums

This is the default enum type. TypeScript assigns numeric values starting from 0.

enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

console.log(Direction.Up);       // 0
console.log(Direction[0]);       // "Up" (reverse mapping)

You can start from a different value:

enum HttpCode {
  OK = 200,
  NotFound = 404,
  ServerError = 500
}

βœ… Reverse mapping works: HttpCode[404] // "NotFound"


3. String Enums

Instead of auto-incremented numbers, you can assign explicit string values:

enum Status {
  Success = "success",
  Error = "error",
  Pending = "pending"
}

These are one-way mappings (no reverse lookup):

console.log(Status.Success); // "success"
console.log(Status["success"]); // ❌ undefined

πŸ‘ Use string enums when values are serialized to APIs or logged for humans.


4. Heterogeneous Enums

Mixing numeric and string values:

enum Mixed {
  No = 0,
  Yes = "YES"
}

⚠️ Avoid this unless necessary β€” it makes your type checks and reverse lookups inconsistent.


5. Computed and Constant Members

Enums can also use expressions:

enum FileSize {
  KB = 1024,
  MB = KB * 1024,
  GB = MB * 1024
}

Or functions (only after constant members):

function getCode() { return 123; }

enum Code {
  A = getCode(), // ❌ error if placed before constants
  B = 1
}


6. Enums at Runtime: How TypeScript Compiles Enums

Numeric Enums:

enum Direction {
  Up,
  Down
}

Compiles to JS:

"use strict";
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));

String Enums:

enum Status {
  Success = "success"
}

Compiles to JS:

"use strict";
var Status;
(function (Status) {
    Status["Success"] = "success";
})(Status || (Status = {}));

βœ… String enums result in simpler output, but lack reverse mapping.


7. Literal Types: Introduction

Literal types allow variables to hold only exact values β€” a powerful alternative to enums when combined with union types.

type Status = "success" | "error" | "pending";

function handle(status: Status) {
  if (status === "error") console.log("Oops!");
}

The variable status cannot be anything other than the specified strings.


8. Union of Literals

type Direction = "up" | "down" | "left" | "right";

function move(dir: Direction) {
  console.log(`Moving ${dir}`);
}

move("up");     // βœ…
move("bottom"); // ❌ Error: not assignable

🧠 Benefits:

  • No enum output at runtime
  • Pure type-level restriction
  • Works great with type guards, discriminated unions, and exhaustive checks

9. Literal Inference with as const

When using object literals, you can preserve literal types using as const:

const status = {
  success: "success",
  error: "error"
} as const;

type Status = typeof status[keyof typeof status]; 
// "success" | "error"

This technique is great for constants without enums.


10. Enums vs Literal Types: When to Use What?

FeatureEnumsLiteral Types
Runtime presenceYesNo (type-only)
Reverse lookupOnly for numeric enumsNo
Code readabilityBetter for grouped constantsCleaner for short values
Use in APIs/loggingString enums recommendedLiteral types work great
ExtendabilityStatic onlyCan be extended with union
Bundle sizeAdds JS outputNo JS output

11. Best Practices

βœ… Prefer string enums over numeric unless using reverse lookup
βœ… Use as const with literal objects for type-safe constants
βœ… Avoid heterogeneous enums (mixed types)
βœ… Use literal types when values are finite and known
βœ… For scalable options, combine enums with utility types


12. Conclusion

Enums and Literal Types in TypeScript are essential for modeling safe, predictable code. Enums give you structure and runtime safety, while literal types offer lightweight, pure type-level restriction.

In short:

  • Use string enums for structured categories
  • Use literal types with unions for inline restrictions
  • Avoid over-engineering β€” pick the right tool based on use case


Mastering Functions in TypeScript: Parameters, Return Types, and Signatures

Functions are a cornerstone of any programming language. In TypeScript, they become even more powerful thanks to static typing. This guide will help you deeply understand how to work with function types, parameters, optional/default values, and type-safe signatures in a practical way.


πŸ”Ή 1. Declaring Functions with Return Type and Parameters

TypeScript allows you to define parameter types and the return type of a function explicitly. This makes your code more predictable and maintainable.

βœ… Basic Syntax

function greet(name: string): string {
  return `Hello, ${name}!`;
}

πŸ” Breakdown

  • name: string β€” The parameter name must be a string.
  • : string β€” The function must return a string.

If you accidentally return a number or forget to return a value, TypeScript will throw an error.

πŸ’‘ Best Practice

Always explicitly declare the return type in public APIs or utility functions to improve readability and debugging.


❓ 2. Optional Parameters in Functions

Sometimes, not all parameters are mandatory. TypeScript allows you to make parameters optional using ?.

βœ… Syntax

function greet(name?: string): string {
  return name ? `Hi, ${name}` : 'Hi, Guest!';
}

πŸ” Notes

  • name?: string makes the name parameter optional.
  • The inferred type becomes: string | undefined

⚠️ Rule

Optional parameters must always come after required parameters.

// ❌ Invalid
function greet(name?: string, message: string): string { ... }

// βœ… Valid
function greet(message: string, name?: string): string { ... }


πŸ›‘οΈ 3. Default Parameters

You can also set default values to parameters so that they fall back to a value if not provided.

βœ… Syntax

function greet(name: string = 'Guest'): string {
  return `Welcome, ${name}`;
}

πŸ” Use Case

This is useful in APIs where you’d like to have fallback behavior without checking undefined manually.

πŸ§ͺ Mixing Required and Default Parameters

function logMessage(type: string, message: string = 'No message') {
  console.log(`[${type}]: ${message}`);
}


πŸ”§ 4. Function Types and Signatures

πŸ“Œ What Is a Function Type?

It defines the shape of a function, just like you define a type for an object.

let add: (x: number, y: number) => number;

add = (a, b) => a + b;

  • This means add is a function that takes two numbers and returns a number.

πŸ” Reusable Function Type using type

type Operation = (a: number, b: number) => number;

const multiply: Operation = (x, y) => x * y;
const subtract: Operation = (x, y) => x - y;

πŸ“¦ Function Signature in Interfaces

interface Logger {
  (message: string, level?: string): void;
}

const consoleLogger: Logger = (msg, lvl = 'info') => {
  console.log(`[${lvl.toUpperCase()}]: ${msg}`);
};


🧠 5. Using Functions with Complex Types

You can pass objects, arrays, or even other functions as parameters.

βœ… Example with Object Destructuring

type User = {
  name: string;
  age?: number;
};

function describeUser({ name, age = 30 }: User): string {
  return `${name} is ${age} years old.`;
}


πŸ§ͺ 6. Returning Functions (Higher-Order Functions)

TypeScript can type functions that return other functions too:

function makeAdder(x: number): (y: number) => number {
  return function(y: number): number {
    return x + y;
  };
}

const add10 = makeAdder(10);
console.log(add10(5)); // 15


🧾 7. Anonymous & Arrow Functions

const square = (n: number): number => n * n;
const log = (msg: string): void => console.log(msg);

  • Arrow functions work great with inline usage and callbacks.
  • Always annotate parameters and return types for clarity.

πŸ“‹ Full Real-World Example

type NotificationType = 'info' | 'warning' | 'error';

interface Notification {
  type: NotificationType;
  message: string;
  user?: string;
}

function sendNotification({ type, message, user = 'System' }: Notification): string {
  return `[${type.toUpperCase()}] (${user}): ${message}`;
}

const note: Notification = {
  type: 'warning',
  message: 'Low disk space'
};

console.log(sendNotification(note));
// Output: [WARNING] (System): Low disk space


πŸ”š Conclusion

Functions in TypeScript provide strong typing features that help you write safer, more robust code:

FeaturePurpose
Return typeEnforces expected output
Parameter typingPrevents unexpected arguments
Optional/defaultAdds flexibility while keeping types intact
Function typesReusable and predictable signatures
Interface-based functionsUseful for callbacks, event handlers, etc.

Here’s a WordPress-friendly, SEO-optimized, beginner-to-advanced guide on:


βœ… Type Aliases vs Interfaces in TypeScript β€” Complete Guide

In TypeScript, both type aliases and interface are powerful tools to define the shape of objects, functions, and more. But which one should you use and when?

In this blog, we’ll explore:

  • βœ… What are Type Aliases?
  • βœ… What are Interfaces?
  • βœ… Key Differences Between type and interface
  • βœ… When to Use Which?
  • βœ… Real-World Examples

🧱 What is a Type Alias?

A type allows you to give a name to any type, including primitives, unions, intersections, tuples, and more.

type User = {
  id: number;
  name: string;
  email?: string;
};

βœ… You can also alias primitives or complex types:

type ID = number | string;
type Coordinates = [number, number];


🧱 What is an Interface?

An interface defines the structure of an object and is mainly used for object shapes and class contracts.

interface User {
  id: number;
  name: string;
  email?: string;
}

βœ… Interfaces are extendable and great for designing large systems.


πŸ” Differences Between Type Alias and Interface

Featuretypeinterface
Can describe object shapeβœ…βœ…
Can describe primitives, unions, tuplesβœ…βŒ
Extend other typesβœ… (&)βœ… (extends)
Merge declarationsβŒβœ…
Use with implements (classes)βœ…βœ…
Recommended for unions/intersectionsβœ…βŒ
Recommended for public APIs / librariesβŒβœ…

🚦 When to Use type vs interface

  • βœ… Use interface when:
    • You’re defining object models or contracts.
    • You need declaration merging (e.g., plugin ecosystems).
    • You work in class-based code.
  • βœ… Use type when:
    • You need unions, intersections, or tuples.
    • You want to alias complex types like (string | number)[].
    • You’re defining utility types or primitives.

πŸ§ͺ Real-World Comparison Example

// Using type
type Admin = {
  role: 'admin';
  permissions: string[];
};

// Using interface
interface Employee {
  id: number;
  name: string;
}

interface Manager extends Employee {
  department: string;
}


βœ… Summary

  • interface is best for object-oriented and extensible design.
  • type is more flexible and better for union/intersection logic.
  • You can use them together β€” it’s not either/or!

πŸ”— Bonus: Can You Combine Them?

Yes!

type UserType = {
  age: number;
};

interface User extends UserType {
  name: string;
}




Understanding Union (&) and Intersection (|) Types in TypeScript

TypeScript’s type system is one of its most powerful features. Two foundational constructs in the system are Union Types (|) and Intersection Types (&).

These types allow you to combine or merge types in flexible ways β€” giving you more control over your data structures.


πŸ”Έ What are Union Types (|)?

Union types allow a value to be one of several types.

πŸ“Œ Example:

type A = string | number;

Here, A can be either a string or a number:

let value: A;

value = "hello"; // βœ… OK
value = 42;      // βœ… OK
value = true;    // ❌ Error

Use union types when you want a variable to support multiple data types.


πŸ”Έ What are Intersection Types (&)?

Intersection types combine multiple types into one. The resulting type has all the properties of the intersected types.

πŸ“Œ Example:

type A = string | number;
type B = A & { extra: boolean };

Let’s break it down:

  • A is a union type (string | number)
  • B is an intersection between A and an object type { extra: boolean }

However, B is never in this example because string | number cannot be intersected with an object { extra: boolean }.

❗ Why?

Because string and number are primitive types, and they don’t share any structure with an object { extra: boolean }. This leads to a contradiction β€” thus B becomes the never type.


βœ… Valid Use of Intersection Types

Let’s see a valid example of intersection:

type Name = { name: string };
type Age = { age: number };

type Person = Name & Age;

const user: Person = {
  name: "Hari",
  age: 30
};

Now Person has both name and age.


πŸ” Combining Both

You can mix union and intersection types:

type Admin = { role: "admin"; access: boolean };
type User = { role: "user"; access: boolean };

type Entity = Admin | User;

type WithMeta = Entity & { createdAt: Date };

Here:

  • Entity is a union type (Admin | User)
  • WithMeta is each of those plus a createdAt field

βœ… When to Use

Use CaseUse
Multiple options (e.g. string or number)`Union (
Require multiple structures togetherIntersection (&)

🧠 Final Thoughts

  • Use unions to accept one of many types
  • Use intersections to merge types into one
  • Be careful when mixing primitive types with object types in intersections – it might result in never.


πŸ›‘οΈ Type Assertions & Type Guards in TypeScript – A Complete Guide

TypeScript is a powerful language that brings static typing to JavaScript. Two of the most useful features for ensuring type safety and clarity are Type Assertions and Type Guards. In this blog, we’ll break them down with examples and use cases.


πŸ“Œ What are Type Assertions?

Type assertions tell TypeScript, β€œI know better – trust me.” You’re basically asserting the type of a variable when TypeScript cannot infer it automatically.

βœ… Syntax

let someValue: any = "I am a string";
let strLength: number = (someValue as string).length;

OR (alternative JSX-incompatible syntax):

let strLength: number = (<string>someValue).length;

🧠 When to Use?

Use type assertions when:

  • TypeScript can’t infer the type.
  • You’re interfacing with third-party APIs.
  • You know something that TypeScript doesn’t.

⚠️ Warning

Type assertions do not change the runtime behavior β€” they’re purely for compile-time checks.


πŸ§ͺ Real-World Example

function getElement(id: string) {
  const el = document.getElementById(id);
  return el as HTMLInputElement; // assert type
}

const input = getElement("username");
input.value = "Hari";

Without the assertion, TypeScript would complain because getElementById returns HTMLElement | null, not specifically HTMLInputElement.


πŸ” What are Type Guards?

Type Guards are runtime checks that validate the type of a variable during execution. They narrow the type within a block of code.

πŸ”§ Built-in Type Guards

  1. typeof
  2. instanceof
  3. in

1️⃣ typeof

Use for primitive types like string, number, boolean.

function printId(id: number | string) {
  if (typeof id === "string") {
    console.log("ID in uppercase:", id.toUpperCase());
  } else {
    console.log("ID doubled:", id * 2);
  }
}


2️⃣ instanceof

Use for class-based objects.

class Dog {
  bark() {}
}

class Cat {
  meow() {}
}

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


3️⃣ in Operator

Checks if a property exists in an object.

type Admin = { name: string; isAdmin: boolean };
type User = { name: string };

function checkAccess(person: Admin | User) {
  if ("isAdmin" in person) {
    console.log("Admin access");
  } else {
    console.log("User access");
  }
}


πŸš€ Custom Type Guards (User-defined)

You can define your own type guard using return type predicates:

type Fish = { swim: () => void };
type Bird = { fly: () => void };

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

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();
  } else {
    pet.fly();
  }
}


πŸ€” Type Assertion vs Type Guard: What’s the Difference?

FeatureType AssertionType Guard
TimingCompile-time onlyRuntime
SafetyRisky – Trusts the developerSafe – Evaluates type before use
PurposeTells TypeScript the typeNarrows type based on condition
Runtime BehaviorNo effectAffects execution flow

🧩 Final Thoughts

  • Use Type Assertions when you’re certain about the type and TypeScript isn’t.
  • Use Type Guards when types may vary at runtime.
  • Combine both for powerful, safe TypeScript code.

πŸ”— Further Reading


Here’s a WordPress-friendly blog post explaining Classes & OOP in TypeScript with public, private, protected, constructors, inheritance, implements, abstract, and static β€” designed to be easy to read and copy-paste into WordPress.


πŸ”· Mastering Classes and OOP in TypeScript: A Beginner-Friendly Guide

Object-Oriented Programming (OOP) in TypeScript provides structure and reusability for your code. Let’s explore key OOP features in TS:


πŸ“¦ 1. public, private, protected

These are access modifiers that control visibility:

  • public (default): Accessible everywhere.
  • private: Accessible only inside the class.
  • protected: Accessible within the class and subclasses.

πŸ“ Example:

class Person {
  public name: string;
  private ssn: string;
  protected age: number;

  constructor(name: string, ssn: string, age: number) {
    this.name = name;
    this.ssn = ssn;
    this.age = age;
  }
}


βš™οΈ 2. Constructors

A constructor is a special method called when a class is instantiated.

πŸ“ Example:

class Animal {
  constructor(public species: string) {
    console.log(`${species} created.`);
  }
}


🧬 3. Inheritance

Use the extends keyword to inherit properties and methods from another class.

πŸ“ Example:

class Dog extends Animal {
  bark() {
    console.log("Woof!");
  }
}


🧩 4. implements – Interface Implementation

Used to enforce structure in a class based on an interface.

πŸ“ Example:

interface Flyable {
  fly(): void;
}

class Bird implements Flyable {
  fly() {
    console.log("Flying high!");
  }
}


🧱 5. abstract Classes

An abstract class can’t be instantiated directly. It can contain abstract methods that must be implemented in child classes.

πŸ“ Example:

abstract class Vehicle {
  abstract move(): void;
}

class Car extends Vehicle {
  move() {
    console.log("Car is moving.");
  }
}


⚑ 6. static Members

Static properties/methods belong to the class itself, not instances.

πŸ“ Example:

class Calculator {
  static PI = 3.14;

  static square(n: number): number {
    return n * n;
  }
}

Use them like Calculator.square(5).


βœ… Summary

FeatureUse Case
publicAccessible everywhere
privateHidden from outside
protectedVisible to class + subclasses
constructorInitialize instance properties
extendsInherit from a base class
implementsEnforce class structure via interfaces
abstractCreate base blueprint classes
staticClass-level methods/properties

πŸ’‘ TypeScript classes give structure and safety to your code.

Here’s a WordPress-friendly deep dive on OOP in TypeScript with real-world, beginner-friendly examples for all major concepts:


πŸ’‘ Deep Dive into Object-Oriented Programming (OOP) in TypeScript

TypeScript supercharges JavaScript with full Object-Oriented Programming (OOP) support: classes, inheritance, encapsulation, abstraction, polymorphism. Let’s explore all key OOP pillars with real-world examples.


1️⃣ Encapsulation – Hide details and expose only essentials

πŸ‘‡ Example: Bank Account

class BankAccount {
  private balance: number;

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

  public deposit(amount: number) {
    this.balance += amount;
  }

  public getBalance(): number {
    return this.balance;
  }
}

const acc = new BankAccount(1000);
acc.deposit(500);
// acc.balance = 10000 ❌ Error (private)
console.log(acc.getBalance()); // βœ… 1500

βœ… balance is encapsulated and protected from direct external access.


2️⃣ Inheritance – Reuse code via extends

πŸ‘‡ Example: Employee and Manager

class Employee {
  constructor(public name: string) {}

  work() {
    console.log(`${this.name} is working`);
  }
}

class Manager extends Employee {
  manage() {
    console.log(`${this.name} is managing`);
  }
}

const mgr = new Manager("Alice");
mgr.work();   // Inherited
mgr.manage(); // Own method

βœ… Manager inherits all features of Employee.


3️⃣ Abstraction – Define a blueprint via abstract

πŸ‘‡ Example: Shape Blueprint

abstract class Shape {
  abstract area(): number;
}

class Circle extends Shape {
  constructor(public radius: number) {
    super();
  }

  area(): number {
    return Math.PI * this.radius * this.radius;
  }
}

βœ… Shape is an abstract class. You must override area() in concrete classes like Circle.


4️⃣ Polymorphism – Same interface, different behavior

πŸ‘‡ Example: Multiple Shape Types

class Square extends Shape {
  constructor(public side: number) {
    super();
  }

  area(): number {
    return this.side * this.side;
  }
}

const shapes: Shape[] = [new Circle(5), new Square(4)];

shapes.forEach(shape => {
  console.log(shape.area());
});

βœ… Both Circle and Square share the same interface but provide different implementations β€” polymorphism.


5️⃣ Interfaces and implements – Ensure structure

interface Logger {
  log(msg: string): void;
}

class ConsoleLogger implements Logger {
  log(msg: string) {
    console.log(`[Log]: ${msg}`);
  }
}

βœ… Enforces that any Logger must have a log() method.


6️⃣ Static Members – Belongs to class, not instance

class Utils {
  static appName = "MyApp";

  static greet(name: string) {
    return `Hello, ${name}!`;
  }
}

console.log(Utils.appName); // MyApp
console.log(Utils.greet("Hari")); // Hello, Hari!

βœ… static properties/methods are accessed on the class itself.


7️⃣ Access Modifiers Recap

ModifierAccessible From
publicAnywhere
privateOnly inside the declaring class
protectedClass + derived subclasses

🧠 Final Thoughts

Object-Oriented Programming in TypeScript helps write:

  • βœ… Clean, modular, reusable code
  • βœ… Real-world scalable applications
  • βœ… Strong contracts via types and interfaces




βš™οΈ Advanced OOP in TypeScript: Mixins, Generics, and Design Patterns

If you’ve mastered classes, inheritance, and interfaces, it’s time to dive into powerful OOP patterns in TypeScript like Mixins, Generics with Classes, and Design Patterns.


1️⃣ Mixins – Reuse behavior across unrelated classes

Mixins let you compose multiple behaviors into a single class, avoiding deep inheritance.

βœ… Example: Timestamp + Logger

type Constructor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    timestamp = new Date();
  };
}

function Loggable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    log() {
      console.log("Logging:", this);
    }
  };
}

class Product {}

const EnhancedProduct = Timestamped(Loggable(Product));

const p = new EnhancedProduct();
p.log(); // Logs with timestamp
console.log(p.timestamp);

βœ… You can combine behaviors without modifying original classes.


2️⃣ Generics with Classes – Write flexible, reusable class code

Generics allow classes to operate on any data type.

βœ… Example: Generic DataStore

class DataStore<T> {
  private items: T[] = [];

  add(item: T) {
    this.items.push(item);
  }

  getAll(): T[] {
    return this.items;
  }
}

const stringStore = new DataStore<string>();
stringStore.add("Hello");

const numberStore = new DataStore<number>();
numberStore.add(42);

βœ… You create type-safe class templates that adapt to different data types.


3️⃣ OOP Design Patterns in TypeScript

Here are 3 popular OOP design patterns implemented in TS:


πŸ—οΈ Factory Pattern

Creates objects without specifying exact class name.

interface Vehicle {
  drive(): void;
}

class Car implements Vehicle {
  drive() { console.log("Driving a car"); }
}

class Bike implements Vehicle {
  drive() { console.log("Riding a bike"); }
}

class VehicleFactory {
  static create(type: string): Vehicle {
    if (type === "car") return new Car();
    if (type === "bike") return new Bike();
    throw new Error("Unknown type");
  }
}

const myVehicle = VehicleFactory.create("car");
myVehicle.drive(); // Driving a car


🧱 Singleton Pattern

Ensures only one instance of a class exists.

class AppConfig {
  private static instance: AppConfig;

  private constructor(public settings: string) {}

  static getInstance() {
    if (!AppConfig.instance) {
      AppConfig.instance = new AppConfig("default");
    }
    return AppConfig.instance;
  }
}

const config = AppConfig.getInstance();


πŸ“ž Observer Pattern

Allows objects to subscribe to state changes.

type Observer = (msg: string) => void;

class Notifier {
  private observers: Observer[] = [];

  subscribe(fn: Observer) {
    this.observers.push(fn);
  }

  notify(message: string) {
    this.observers.forEach(fn => fn(message));
  }
}

const news = new Notifier();
news.subscribe(msg => console.log("User1:", msg));
news.notify("OOP Blog Published!");


🧠 Final Thoughts

FeaturePurpose
MixinsReuse behavior across classes
GenericsType-safe flexible classes
Design PatternsStandard solutions to OOP problems

Here’s a WordPress-friendly deep dive into three advanced OOP design patterns in TypeScript: Decorator, Strategy, and Command β€” with real-world examples and explanations.


🎯 Advanced OOP Design Patterns in TypeScript – Decorator, Strategy & Command

TypeScript supports powerful design patterns that help you write clean, modular, and scalable code. In this post, we’ll explore three top OOP patterns:


1️⃣ Decorator Pattern – Add functionality without modifying the original class

The Decorator Pattern allows you to wrap objects with new behavior.

βœ… Example: Logging Decorator

function LogExecution(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    return original.apply(this, args);
  };
}

class UserService {
  @LogExecution
  getUser(id: number) {
    return { id, name: "Hari" };
  }
}

const service = new UserService();
service.getUser(101);

βœ… Adds logging without touching the original method.


2️⃣ Strategy Pattern – Choose behavior at runtime

The Strategy Pattern lets you switch algorithms easily.

βœ… Example: Payment Strategies

interface PaymentStrategy {
  pay(amount: number): void;
}

class CreditCardPayment implements PaymentStrategy {
  pay(amount: number) {
    console.log(`Paid β‚Ή${amount} using Credit Card.`);
  }
}

class UpiPayment implements PaymentStrategy {
  pay(amount: number) {
    console.log(`Paid β‚Ή${amount} via UPI.`);
  }
}

class Checkout {
  constructor(private strategy: PaymentStrategy) {}

  process(amount: number) {
    this.strategy.pay(amount);
  }
}

const payWithUPI = new Checkout(new UpiPayment());
payWithUPI.process(500);

βœ… Easily plug and switch between strategies (CreditCard, UPI, PayPal, etc.)


3️⃣ Command Pattern – Encapsulate requests as objects

The Command Pattern encapsulates user actions as objects. Great for Undo/Redo, Queues, UI events.

βœ… Example: Remote Control Commands

interface Command {
  execute(): void;
}

class LightOnCommand implements Command {
  execute() {
    console.log("Light turned ON");
  }
}

class LightOffCommand implements Command {
  execute() {
    console.log("Light turned OFF");
  }
}

class RemoteControl {
  private commands: Command[] = [];

  press(command: Command) {
    this.commands.push(command);
    command.execute();
  }
}

const remote = new RemoteControl();
remote.press(new LightOnCommand());
remote.press(new LightOffCommand());

βœ… You can store, queue, or undo commands later.


🧠 Summary Table

PatternUse CaseBenefit
DecoratorAdd new behavior to existing codeNo need to change original class
StrategySwap algorithms or logic dynamicallyClean, pluggable logic
CommandEncapsulate actions or eventsUndo/redo, queuing, history tracking

πŸ’‘ These patterns help build maintainable enterprise apps with TypeScript.


Drop a comment or share the topic you want next!


Awesome! Here’s a WordPress-friendly deep dive covering all three requested OOP pattern sets with real-world TypeScript examples:


🧠 OOP Design Patterns in TypeScript: Observer vs Pub/Sub, State, Mediator, Builder & Adapter

Let’s explore 3 sets of powerful and real-world design patterns in TypeScript:


πŸ” 1️⃣ Observer vs Pub/Sub

Though similar, Observer and Publish/Subscribe (Pub/Sub) differ in structure and usage.

πŸ” Observer Pattern – Objects subscribe directly to another object

type Observer = (message: string) => void;

class Subject {
  private observers: Observer[] = [];

  subscribe(fn: Observer) {
    this.observers.push(fn);
  }

  notify(message: string) {
    for (const obs of this.observers) {
      obs(message);
    }
  }
}

const subject = new Subject();
subject.subscribe(msg => console.log("Observer1:", msg));
subject.notify("New post published!");

βœ… Tight coupling – observers know who they observe.


πŸ“£ Pub/Sub Pattern – Mediated via an event bus

class EventBus {
  private listeners: { [key: string]: Function[] } = {};

  subscribe(event: string, fn: Function) {
    (this.listeners[event] ||= []).push(fn);
  }

  publish(event: string, data: any) {
    this.listeners[event]?.forEach(fn => fn(data));
  }
}

const bus = new EventBus();
bus.subscribe("user:created", data => console.log("New user:", data));
bus.publish("user:created", { name: "Hari" });

βœ… Loose coupling – subscribers don’t know publishers.


πŸ”„ 2️⃣ State Pattern – Change behavior based on internal state

βœ… Example: Media Player

interface PlayerState {
  play(): void;
  pause(): void;
}

class PlayingState implements PlayerState {
  pause() {
    console.log("Paused the video.");
  }
  play() {
    console.log("Already playing.");
  }
}

class PausedState implements PlayerState {
  pause() {
    console.log("Already paused.");
  }
  play() {
    console.log("Resuming playback.");
  }
}

class VideoPlayer {
  private state: PlayerState = new PausedState();

  setState(state: PlayerState) {
    this.state = state;
  }

  play() {
    this.state.play();
  }

  pause() {
    this.state.pause();
  }
}

const player = new VideoPlayer();
player.play(); // Resuming playback
player.setState(new PlayingState());
player.pause(); // Paused the video

βœ… The behavior changes based on the current state object.


🀝 3️⃣ Mediator Pattern – Centralize complex communication

βœ… Example: Chat Room Mediator

class ChatRoom {
  showMessage(user: User, message: string) {
    console.log(`[${user.name}]: ${message}`);
  }
}

class User {
  constructor(public name: string, private chatRoom: ChatRoom) {}

  send(message: string) {
    this.chatRoom.showMessage(this, message);
  }
}

const chatRoom = new ChatRoom();
const user1 = new User("Alice", chatRoom);
const user2 = new User("Bob", chatRoom);

user1.send("Hello!");
user2.send("Hey there!");

βœ… The chat room mediates communication β€” users don’t talk to each other directly.


🧱 4️⃣ Builder Pattern – Create complex objects step-by-step

class Car {
  constructor(
    public engine: string,
    public wheels: number,
    public sunroof: boolean
  ) {}
}

class CarBuilder {
  private engine = "Standard";
  private wheels = 4;
  private sunroof = false;

  setEngine(engine: string) {
    this.engine = engine;
    return this;
  }

  setWheels(wheels: number) {
    this.wheels = wheels;
    return this;
  }

  addSunroof() {
    this.sunroof = true;
    return this;
  }

  build() {
    return new Car(this.engine, this.wheels, this.sunroof);
  }
}

const car = new CarBuilder().setEngine("V8").addSunroof().build();
console.log(car);

βœ… Flexible way to create objects with optional parts.


πŸ”Œ 5️⃣ Adapter Pattern – Bridge incompatible interfaces

βœ… Example: Legacy API adapter

class OldPrinter {
  printText(text: string) {
    console.log("Printing:", text);
  }
}

interface NewPrinter {
  print(data: string): void;
}

class PrinterAdapter implements NewPrinter {
  constructor(private oldPrinter: OldPrinter) {}

  print(data: string) {
    this.oldPrinter.printText(data);
  }
}

const adapter = new PrinterAdapter(new OldPrinter());
adapter.print("Hello World");

βœ… Use modern interfaces with legacy classes.


πŸ“Œ Summary Table

PatternUse CaseKey Benefit
ObserverDirect subscriptionTight coupling
Pub/SubEvent-driven communicationLoose coupling
StateChange behavior based on stateCleaner conditional logic
MediatorCentral communication hubReduces inter-object dependency
BuilderStep-by-step object constructionCustomizable instantiation
AdapterWrap incompatible APIsEnsures compatibility

πŸš€ Final Thoughts

Mastering these patterns enables you to write maintainable, extensible, and enterprise-level applications in TypeScript.


Sure! Below is a complete TypeScript example for a simplified enterprise-level Order Management System (OMS) with clean architecture concepts like:

  • Interfaces & Abstraction
  • Service layer
  • Repository pattern
  • Dependency Injection
  • Models, DTOs, Validation

🏒 Enterprise-Level Application in TypeScript: Order Management System (OMS)

πŸ“ Project Structure

src/
β”œβ”€β”€ controllers/
β”‚   └── OrderController.ts
β”œβ”€β”€ services/
β”‚   └── OrderService.ts
β”œβ”€β”€ repositories/
β”‚   └── OrderRepository.ts
β”œβ”€β”€ models/
β”‚   └── Order.ts
β”œβ”€β”€ dtos/
β”‚   └── CreateOrderDTO.ts
β”œβ”€β”€ app.ts


1️⃣ models/Order.ts

export interface Order {
  id: string;
  customerName: string;
  product: string;
  quantity: number;
  createdAt: Date;
}


2️⃣ dtos/CreateOrderDTO.ts

export interface CreateOrderDTO {
  customerName: string;
  product: string;
  quantity: number;
}


3️⃣ repositories/OrderRepository.ts

import { Order } from "../models/Order";

export interface IOrderRepository {
  create(order: Order): Order;
  findAll(): Order[];
}

export class OrderRepository implements IOrderRepository {
  private orders: Order[] = [];

  create(order: Order): Order {
    this.orders.push(order);
    return order;
  }

  findAll(): Order[] {
    return this.orders;
  }
}


4️⃣ services/OrderService.ts

import { CreateOrderDTO } from "../dtos/CreateOrderDTO";
import { Order } from "../models/Order";
import { IOrderRepository } from "../repositories/OrderRepository";
import { v4 as uuidv4 } from "uuid";

export class OrderService {
  constructor(private repo: IOrderRepository) {}

  createOrder(data: CreateOrderDTO): Order {
    const order: Order = {
      id: uuidv4(),
      customerName: data.customerName,
      product: data.product,
      quantity: data.quantity,
      createdAt: new Date()
    };

    return this.repo.create(order);
  }

  getOrders(): Order[] {
    return this.repo.findAll();
  }
}


5️⃣ controllers/OrderController.ts

import { Request, Response } from "express";
import { OrderService } from "../services/OrderService";

export class OrderController {
  constructor(private service: OrderService) {}

  create = (req: Request, res: Response) => {
    try {
      const order = this.service.createOrder(req.body);
      res.status(201).json(order);
    } catch (err) {
      res.status(400).json({ error: "Failed to create order" });
    }
  };

  list = (_: Request, res: Response) => {
    const orders = this.service.getOrders();
    res.status(200).json(orders);
  };
}


6️⃣ app.ts – Entry point

import express from "express";
import { OrderRepository } from "./repositories/OrderRepository";
import { OrderService } from "./services/OrderService";
import { OrderController } from "./controllers/OrderController";

const app = express();
const port = 4000;

app.use(express.json());

// DI Setup
const orderRepo = new OrderRepository();
const orderService = new OrderService(orderRepo);
const orderController = new OrderController(orderService);

// Routes
app.post("/orders", orderController.create);
app.get("/orders", orderController.list);

app.listen(port, () => {
  console.log(`πŸš€ Server is running on http://localhost:${port}`);
});


πŸ§ͺ Test with cURL or Postman

POST /orders

{
  "customerName": "Hari",
  "product": "TypeScript Book",
  "quantity": 2
}


βœ… Key Enterprise Concepts Used

ConceptWhere Used
InterfacesIOrderRepository, Order, DTOs
Abstraction LayerService, Repository
Dependency InjectionManual in app.ts
Separation of ConcernsClean folder structure
DTOsValidated input in CreateOrderDTO

πŸ’‘ Final Thoughts

This example shows how you can build maintainable, testable, and scalable enterprise-grade apps in TypeScript with clean architecture.

Awesome! Let’s build this Enterprise-Level TypeScript App step by step with full-stack readiness.


πŸš€ Enterprise OMS Boilerplate (Enhanced Version)

We already have the clean architecture:
Models β†’ DTOs β†’ Repository β†’ Service β†’ Controller β†’ App

Now let’s enhance it with:


βœ… 1. Unit Testing with Jest

Install Jest & TS support

npm install --save-dev jest ts-jest @types/jest
npx ts-jest config:init

Example: OrderService.test.ts

import { OrderService } from '../services/OrderService';
import { OrderRepository } from '../repositories/OrderRepository';

describe('OrderService', () => {
  const repo = new OrderRepository();
  const service = new OrderService(repo);

  it('should create an order', () => {
    const order = service.createOrder({
      customerName: "Hari",
      product: "TS Book",
      quantity: 1
    });

    expect(order).toHaveProperty("id");
    expect(order.customerName).toBe("Hari");
  });
});

Run tests with:

npx jest


πŸ”— 2. MongoDB Integration

Install MongoDB and Mongoose:

npm install mongoose

Update Order.ts model

import mongoose from "mongoose";

const OrderSchema = new mongoose.Schema({
  customerName: String,
  product: String,
  quantity: Number,
  createdAt: { type: Date, default: Date.now }
});

export default mongoose.model("Order", OrderSchema);

Update OrderRepository.ts

import OrderModel from "../models/Order";

export class OrderRepository {
  async create(data: any) {
    return await OrderModel.create(data);
  }

  async findAll() {
    return await OrderModel.find();
  }
}

Add MongoDB connection to app.ts

import mongoose from 'mongoose';

mongoose.connect('mongodb://localhost:27017/oms')
  .then(() => console.log("MongoDB Connected"));


πŸ›‘οΈ 3. DTO Validation using Zod

Install:

npm install zod

Update CreateOrderDTO.ts

import { z } from 'zod';

export const CreateOrderSchema = z.object({
  customerName: z.string().min(1),
  product: z.string().min(1),
  quantity: z.number().positive()
});

export type CreateOrderDTO = z.infer<typeof CreateOrderSchema>;

Validate in OrderController.ts

import { CreateOrderSchema } from "../dtos/CreateOrderDTO";

create = (req: Request, res: Response) => {
  try {
    const data = CreateOrderSchema.parse(req.body);
    const order = this.service.createOrder(data);
    res.status(201).json(order);
  } catch (err: any) {
    res.status(400).json({ error: err.errors || err.message });
  }
};


πŸš€ 4. CI/CD-Ready Boilerplate Setup

βœ… Suggested Folder Structure

src/
β”œβ”€β”€ config/           # DB, environment config
β”œβ”€β”€ controllers/
β”œβ”€β”€ dtos/
β”œβ”€β”€ models/
β”œβ”€β”€ repositories/
β”œβ”€β”€ services/
β”œβ”€β”€ tests/
β”œβ”€β”€ app.ts
β”œβ”€β”€ server.ts

βœ… package.json scripts

"scripts": {
  "start": "ts-node src/server.ts",
  "dev": "nodemon src/server.ts",
  "test": "jest",
  "build": "tsc"
}

βœ… Dockerfile (Optional for deployment)

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]


πŸ“¦ Deployment Ready

  • Add .env with DB connection string
  • Use GitHub Actions or CircleCI for CI/CD
  • Docker + MongoDB setup for production

Perfect! Below is the complete setup for your enterprise-grade TypeScript app with:

  • βœ… .env for MongoDB config
  • βœ… GitHub Actions CI/CD workflow
  • βœ… 🐳 Docker + MongoDB setup for production

πŸ“ Final Folder Structure (Highlights)

project-root/
β”œβ”€β”€ src/
β”‚   └── ...
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       └── ci.yml
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ .env
β”œβ”€β”€ .env.example
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ jest.config.js
└── package.json


1️⃣ .env & .env.example

βœ… .env.example

PORT=4000
MONGODB_URI=mongodb://localhost:27017/oms

βœ… In app.ts

import dotenv from "dotenv";
dotenv.config();

mongoose.connect(process.env.MONGODB_URI!)
  .then(() => console.log("βœ… MongoDB connected"))
  .catch(err => console.error("❌ MongoDB error", err));


2️⃣ 🐳 Docker + MongoDB Setup

βœ… Dockerfile

FROM node:18

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

RUN npm run build

CMD ["node", "dist/server.js"]

βœ… docker-compose.yml

version: "3.8"

services:
  app:
    build: .
    ports:
      - "4000:4000"
    environment:
      - MONGODB_URI=mongodb://mongo:27017/oms
    depends_on:
      - mongo

  mongo:
    image: mongo:6
    container_name: oms-mongo
    ports:
      - "27017:27017"
    volumes:
      - oms_data:/data/db

volumes:
  oms_data:

To start the app with MongoDB:

docker-compose up --build


3️⃣ βš™οΈ GitHub Actions CI/CD

βœ… .github/workflows/ci.yml

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-test:
    runs-on: ubuntu-latest
    services:
      mongo:
        image: mongo:6
        ports:
          - 27017:27017

    env:
      MONGODB_URI: mongodb://localhost:27017/test-db

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18

      - name: Install deps
        run: npm install

      - name: Run tests
        run: npm run test


βœ… Summary

ToolPurpose
.envManages secrets, DB URLs, ports
DockerContainerizes the app
Docker ComposeRuns MongoDB with app in dev/prod
GitHub ActionsCI/CD pipeline for test/build


πŸ“¦ TypeScript Modules Explained: import, export, Default vs Named, esModuleInterop & allowSyntheticDefaultImports

Modules are the backbone of modern TypeScript and JavaScript development. They help you organize code, reuse logic, and work cleanly in teams. In this guide, you’ll learn:

  • πŸ”„ What are modules?
  • πŸ” Difference between default and named exports
  • ✨ import and export syntax
  • βš™οΈ TypeScript compiler flags: esModuleInterop and allowSyntheticDefaultImports
  • Best practices + real examples

πŸ“˜ 1. What is a Module in TypeScript?

A module is any file with its own scope. If a file has import or export keywords, it is considered a module. Modules allow you to:

Split code across multiple files
Import only what you need
Avoid global scope pollution

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

// app.ts
import { add } from './math';
console.log(add(2, 3)); // 5


2. Export Syntax – Named vs Default

πŸ”· Named Export

You can export multiple items by name:

// shapes.ts
export const PI = 3.14;
export function areaCircle(r: number): number {
  return PI * r * r;
}

// main.ts
import { PI, areaCircle } from './shapes';

You must import with the exact name used in the export.


Default Export

Each file can have only one default export:

// logger.ts
export default function log(message: string): void {
  console.log("LOG:", message);
}

// main.ts
import log from './logger';
log("App started");

You can name the imported value anything you want.


3. Mixing Default and Named Exports

You can export both default and named from the same file:

// user.ts
export default class User {
  constructor(public name: string) {}
}
export const MAX_USERS = 100;

// main.ts
import User, { MAX_USERS } from './user';


4. Common Pitfall: Interop with CommonJS

When you work with Node.js or libraries like express, you might face this issue:

// Without esModuleInterop
import express from 'express'; // ❌ Error: cannot use default import


5. Understanding esModuleInterop and allowSyntheticDefaultImports

esModuleInterop

Enables compatibility with CommonJS modules that don’t have default exports.

{
  "compilerOptions": {
    "esModuleInterop": true
  }
}

βœ” Allows this:

import express from 'express'; // Works like default import

Without it, you must do:

import * as express from 'express';


allowSyntheticDefaultImports

This is similar to esModuleInterop but doesn’t affect the output β€” only type-checking.

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true
  }
}

βœ” Helpful when you’re using Babel or Webpack that support default-style imports even for CommonJS.

πŸ“Œ Usually, esModuleInterop implies allowSyntheticDefaultImports, so you rarely need both explicitly.


6. Real Example with Express

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "ES6"
  }
}

// server.ts
import express from 'express';

const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
app.listen(3000);

Without esModuleInterop, you’ll get this error:

Module ‘”express”‘ has no default export.


7. Best Practices

Use named exports when you have multiple exports
Use default export if the file has one main entity
Enable esModuleInterop in most Node.js projects
Avoid mixing CommonJS and ESModules unless needed
Use file extensions when working with ESModules in Node 18+


πŸ’‘ Summary Table

FeatureDescriptionDefault?
exportMakes functions/classes/constants available to other files❌
default exportExports a single main value❌
esModuleInteropEnables default import from CommonJS❌
allowSyntheticDefaultImportsAllows default import syntax in type-checking❌



🧠 TypeScript Generics: A Complete Guide

Generics in TypeScript provide reusability, type safety, and flexibility. They let you create components that work with multiple types instead of a single one, without losing type information.


πŸ“¦ 1. Generic Functions

Instead of hardcoding types, we use a placeholder (like T) and let the compiler infer or receive the type.

βœ… Example:

function identity<T>(arg: T): T {
  return arg;
}

identity<string>('hello'); // "hello"
identity<number>(42);      // 42

  • T is a type variable.
  • You can call it identity<string>('hello') or let TS infer the type: identity('hello').

🧱 2. Generic Interfaces

Interfaces can also use generics to enforce structure with dynamic types.

interface Box<T> {
  value: T;
}

const numberBox: Box<number> = { value: 100 };
const stringBox: Box<string> = { value: "TypeScript" };


πŸ— 3. Generic Classes

Classes can be made generic and reusable across different data types.

class Stack<T> {
  private items: T[] = [];

  push(item: T) {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const numStack = new Stack<number>();
numStack.push(1);

const stringStack = new Stack<string>();
stringStack.push("hello");


β›“ 4. Constraints with Generics (extends)

Sometimes, you want to restrict the generic type to meet certain conditions.

interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("hello");        // βœ… Works
logLength([1, 2, 3]);      // βœ… Works
// logLength(123);         // ❌ Error: number doesn't have length


βš™οΈ 5. Default Type Parameters

You can provide default types if none are supplied.

interface ApiResponse<T = any> {
  data: T;
  status: number;
}

const res1: ApiResponse<string> = { data: "ok", status: 200 };
const res2: ApiResponse = { data: { id: 1 }, status: 200 }; // defaults to `any`


✨ Benefits of Using Generics

Reusability
Type Safety
IDE Autocomplete
Better Code Maintainability


Real-World Use Cases

  • Reusable Form Components
  • Utility Functions (e.g., filter, map)
  • API Response Wrappers
  • Data Structures like Stack, Queue, List

Mastering generics is essential for writing clean, reusable, and scalable TypeScript code.




Must-Know TypeScript Utility Types (With Deep Examples)

TypeScript’s utility types allow you to transform, extract, and reshape types in a powerful and type-safe way. Below is a deep dive with practical examples, real-world use cases, and dry runs for each.


πŸ”Ή 1. Partial<T> – Make all properties optional

type User = {
  id: number;
  name: string;
  email: string;
};

function updateUser(id: number, updates: Partial<User>) {
  // updates can have any subset of User fields
}

updateUser(1, { name: "Hari" });

🧠 Use Case: PATCH APIs where users can update only selected fields.

πŸ“Œ Dry Run:

  • Input: { name: "Hari" }
  • Type of updates: { id?: number; name?: string; email?: string; }

πŸ”Ή 2. Required<T> – Make all properties required

type Config = {
  port?: number;
  host?: string;
};

const fullConfig: Required<Config> = {
  port: 3000,
  host: "localhost"
};

🧠 Use Case: Ensuring all fields are filled before starting server.

πŸ“Œ Dry Run:

  • Type becomes: { port: number; host: string; }

πŸ”Ή 3. Readonly<T> – Prevent modification

type Todo = {
  id: number;
  title: string;
};

const todo: Readonly<Todo> = {
  id: 1,
  title: "Learn TypeScript"
};

todo.title = "Master TS"; // ❌ Error

🧠 Use Case: Immutability in shared objects like Redux state.


πŸ”Ή 4. Pick<T, K> – Select specific properties

type Employee = {
  id: number;
  name: string;
  department: string;
  salary: number;
};

type PublicInfo = Pick<Employee, "id" | "name">;

const info: PublicInfo = { id: 101, name: "Alice" };

🧠 Use Case: Show only safe fields in UI/public API.


πŸ”Ή 5. Omit<T, K> – Remove specific properties

type PrivateEmployee = Omit<Employee, "salary">;

const view: PrivateEmployee = {
  id: 101,
  name: "Alice",
  department: "HR"
};

🧠 Use Case: Hide confidential data.

πŸ“Œ Dry Run:

  • Omitted: "salary"
  • Final type: { id: number; name: string; department: string }

πŸ”Ή 6. Record<K, T> – Map keys to a specific type

type Role = "admin" | "user" | "guest";

const permissions: Record<Role, string[]> = {
  admin: ["create", "read", "update", "delete"],
  user: ["read"],
  guest: []
};

🧠 Use Case: Role-based access control.

πŸ“Œ Dry Run:

  • Keys: 'admin' | 'user' | 'guest'
  • Values: string[]

πŸ”Ή 7. Exclude<T, U> – Remove from union

type Roles = "admin" | "user" | "superadmin";

type WithoutSuper = Exclude<Roles, "superadmin">;
// 'admin' | 'user'

🧠 Use Case: Prevent elevated roles in restricted components.


πŸ”Ή 8. Extract<T, U> – Extract common members

type A = "dog" | "cat" | "mouse";
type B = "dog" | "lion";

type Common = Extract<A, B>; // "dog"

🧠 Use Case: Find valid intersection of two sets.

πŸ“Œ Dry Run:

  • A ∩ B = "dog"

πŸ”Ή 9. NonNullable<T> – Remove null and undefined

type Input = string | null | undefined;

type Clean = NonNullable<Input>; // string

🧠 Use Case: Validate input before using directly.


πŸ”Ή Bonus Combos 🎁

βœ… Combine Pick + Partial:

type Product = {
  id: string;
  name: string;
  price: number;
  description: string;
};

type EditableProduct = Partial<Pick<Product, "name" | "description">>;

const edit: EditableProduct = {
  name: "New Name"
};

πŸ“Œ Use Case: Editable fields in a form.


βœ… Recap Table

UtilityWhat it DoesBest For
Partial<T>Makes all properties optionalPATCH APIs
Required<T>Makes all properties requiredConfig enforcement
Readonly<T>Freezes propertiesImmutable state
Pick<T,K>Select specific fieldsPublic views
Omit<T,K>Remove selected fieldsHide sensitive data
Record<K,T>Create typed object mapsConfig, role-permission map
Exclude<T,U>Remove union membersFilter specific types
Extract<T,U>Keep matching union membersGet common subset
NonNullable<T>Remove null/undefinedSafer values

πŸ”š Conclusion

TypeScript utility types are like power tools β€” they simplify your code and make it safer. Use them to build clean, scalable, and robust applications.

🧠 Want examples for ReturnType, InstanceType, Awaited, or custom utility types? Drop a comment and let’s go deeper!



Advanced TypeScript: Mastering Type Manipulation

Conditional Types, Mapped Types & Template Literal Types Explained

TypeScript’s type system isn’t just about safety β€” it’s powerful and expressive, allowing deep type-level computation and transformations. In this post, we’ll explore Type Manipulation techniques every serious TypeScript developer must know: Conditional Types, Mapped Types, and Template Literal Types.


🧠 Table of Contents

  1. What Are Type Manipulations?
  2. πŸ“Œ Conditional Types
  3. πŸ” Mapped Types
  4. πŸ”€ Template Literal Types
  5. 🎯 Bonus: Key Remapping
  6. πŸ“š Use Cases in Real Projects
  7. πŸš€ Final Thoughts

What Are Type Manipulations?

TypeScript allows you to compute new types based on existing ones using logic β€” just like you’d do in JavaScript at runtime. This is useful for:

  • Creating generic utility types
  • Enforcing rules across complex object structures
  • Avoiding repetition in large codebases

πŸ“Œ Conditional Types

Conditional types use the ternary syntax to decide between types at compile-time:

T extends U ? X : Y

βœ… Example:

type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<42>;      // false

This works like an if statement for types!


πŸ” Mapped Types

Mapped types allow you to transform all properties of a type in one go.

type Mapped = {
  [K in keyof T]: SomeTransformation<T[K]>;
}

βœ… Example: Make all properties optional

type Optional<T> = {
  [K in keyof T]?: T[K];
};

type User = { name: string; age: number };
type OptionalUser = Optional<User>;
// { name?: string; age?: number }

Other useful examples:

  • Readonly<T>
  • Partial<T>
  • Record<K, T>

πŸ”€ Template Literal Types

Introduced in TypeScript 4.1, these allow types to behave like string templates.

type EventName = `on${Capitalize<string>}`;

βœ… Example:

type Color = 'red' | 'green';
type ColoredBox = `box-${Color}`; 
// 'box-red' | 'box-green'

You can combine it with conditional types for powerful inference!


🎯 Bonus: Key Remapping in Mapped Types

Added in TypeScript 4.1, this allows you to rename keys during type transformation.

βœ… Example:

type PrefixKeys<T> = {
  [K in keyof T as `prefix_${string & K}`]: T[K];
};

type Original = { id: number; name: string };
type Prefixed = PrefixKeys<Original>;
// { prefix_id: number; prefix_name: string }

You can even filter keys by using never:

type ExcludeId<T> = {
  [K in keyof T as K extends 'id' ? never : K]: T[K];
};


πŸ“š Use Cases in Real Projects

TechniqueUse Case
ConditionalType-based branching logic (API response handler, etc.)
MappedTransforming DTOs, response shaping
TemplateEvent names, key builders, tag/slug formats
RemappingRename fields, exclude/include certain fields dynamically

πŸ” Save this blog
πŸ’¬ Have a favorite use case? Share it below!


✨ Related Topics:

  • Utility Types (Pick, Omit, Extract)
  • infer keyword for advanced inference
  • Discriminated Unions


πŸ” TypeScript Power Tools: Utility Types, Inference with infer, and Discriminated Unions

Want to write smarter TypeScript code that scales?
Today we’ll explore some of the most powerful and practical tools in TypeScript’s type system:

  • Built-in Utility Types: Pick, Omit, Extract
  • Advanced Inference with infer
  • Robust Pattern Matching with Discriminated Unions

βš’οΈ Utility Types

βœ… Pick<T, K>

Creates a new type by picking specific properties from another type.

type User = {
  id: number;
  name: string;
  email: string;
};

type PublicUser = Pick<User, 'id' | 'name'>;
// { id: number; name: string }

Use when you want to expose only certain fields in APIs or components.


βœ… Omit<T, K>

Creates a type by omitting properties from another type.

type AdminUser = Omit<User, 'email'>;
// { id: number; name: string }

Perfect for removing sensitive or unnecessary fields.


βœ… Extract<T, U>

Creates a type by extracting types that are assignable to a union.

type Roles = 'admin' | 'editor' | 'viewer';
type ReadOnlyRoles = Extract<Roles, 'viewer' | 'editor'>;
// 'editor' | 'viewer'

Helpful for filtering out specific variants.


🧠 infer Keyword – Custom Type Inference

The infer keyword allows you to capture part of a type inside a conditional type.

βœ… Example: Extract Return Type

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Fn = () => string;
type R = ReturnType<Fn>; // string

You’re saying: β€œIf T is a function, infer the return type R.”


βœ… Example: Get Array Element Type

type ElementType<T> = T extends (infer U)[] ? U : never;

type A = ElementType<string[]>; // string

This lets you build custom utility types beyond the built-ins.


βš–οΈ Discriminated Unions

Discriminated unions are union types with a shared literal field (the β€œdiscriminator”), allowing precise type narrowing.

βœ… Example:

type Circle = { kind: 'circle'; radius: number };
type Square = { kind: 'square'; side: number };
type Shape = Circle | Square;

function getArea(shape: Shape): number {
  if (shape.kind === 'circle') {
    return Math.PI * shape.radius ** 2;
  } else {
    return shape.side ** 2;
  }
}

The key:

  • The shared field kind enables TypeScript to narrow down the exact shape variant
  • Helps you avoid manual type assertions or unsafe logic

πŸ› οΈ Real-World Use Cases

FeatureUse Case
Pick / OmitForm validation, public vs private types
ExtractFilter roles, permissions, enums
inferBuild reusable type helpers (e.g. ExtractParamType)
Discriminated UnionsRedux actions, API response unions, event handlers

πŸš€ Final Thoughts

TypeScript isn’t just for type checking β€” it’s a powerful language for modeling your application logic.
By using these tools:

  • Your code is safer
  • Your types are DRY
  • Your architecture is more expressive

πŸ’¬ Which of these do you use the most?
πŸ” Repost or save if you’re diving deep into advanced TypeScript!


✨ Related Reads:



πŸš€ Deep TypeScript: Advanced Mapped Types, Template Literals & Type Design Patterns

TypeScript’s type system can model everything from object shapes to string patterns to behavior over time. In this post, we’ll level up your type-fu with:

  1. Advanced Mapped Types – with modifiers, filtering, and remapping
  2. Template Literal Types – types as dynamic string patterns
  3. Design Patterns with Types – real-world architecture using types alone

πŸ” 1. Advanced Mapped Types

Mapped types iterate over keys to generate new types. But TypeScript lets you go way beyond basic mapping.

πŸ“Œ Basics Refresher

type Optional<T> = {
  [K in keyof T]?: T[K];
};


πŸ”§ Modifier Tweaks: + and -

type Required<T> = {
  [K in keyof T]-?: T[K]; // removes optional
};

type Readonly<T> = {
  +readonly [K in keyof T]: T[K]; // adds readonly
};

Use + and - to explicitly add/remove modifiers.


πŸ”„ Key Remapping (as)

Remap property names:

type Prefix<T> = {
  [K in keyof T as `prefix_${string & K}`]: T[K];
};

type Original = { id: number; name: string };
type Prefixed = Prefix<Original>; 
// { prefix_id: number; prefix_name: string }

You can also use conditional logic to filter keys:

type WithoutId<T> = {
  [K in keyof T as K extends 'id' ? never : K]: T[K];
};


🧠 Mapped Type Utilities

  • Pick<T, K>: Select some keys
  • Omit<T, K>: Remove some keys
  • Record<K, T>: Create new objects from union keys
  • Partial<T>: Make everything optional
  • Readonly<T>: Make everything immutable

πŸ”€ 2. Template Literal Types

Introduced in TS 4.1, these let you build string-based types dynamically.

πŸ”‘ Basic Usage:

type Event = `on${Capitalize<string>}`;

🎯 Practical Example:

type Lang = 'en' | 'fr' | 'de';
type TransKey = `title_${Lang}`;
// "title_en" | "title_fr" | "title_de"


πŸ§ͺ Combine With Unions:

type Color = 'red' | 'blue';
type Variant = 'light' | 'dark';

type ClassNames = `${Color}-${Variant}`;
// "red-light" | "red-dark" | "blue-light" | "blue-dark"

πŸ” Enforce Specific Formats:

type ISODate = `${number}-${number}-${number}`;
// e.g. "2023-08-07"

Great for ensuring key naming, localization keys, API slugs, etc.


🧩 3. Design Patterns with TypeScript Types

TypeScript types are expressive enough to represent software patterns and contracts.


πŸ“¦ DTO Transformation

type User = { id: number; name: string; password: string };
type SafeUser = Omit<User, 'password'>;


🧱 Union Tagging (Discriminated Union)

type Action =
  | { type: 'LOAD_USER'; payload: string }
  | { type: 'LOGOUT' };

function reducer(state: any, action: Action) {
  switch (action.type) {
    case 'LOAD_USER':
      return { user: action.payload };
    case 'LOGOUT':
      return {};
  }
}


πŸ”„ Reusable Transformation Utilities

type SnakeToCamel<S> = 
  S extends `${infer Head}_${infer Tail}` 
    ? `${Head}${Capitalize<SnakeToCamel<Tail>>}`
    : S;

type Result = SnakeToCamel<'first_name'>; // "firstName"

Use infer, template literals, and recursion to model string transformations!


πŸ“š Types as Contracts

  • ApiResponse<T> – typed REST/GQL response
  • ComponentProps<T> – generic prop injection
  • FormErrors<T> – partial error types for each field
  • EventMap<K> – event-to-payload mapping

Final Thoughts

Advanced TypeScript features can:

  • Express design patterns declaratively
  • Prevent bugs before runtime
  • Enhance developer productivity

By combining mapped types, template literals, and pattern-based type design, you gain full control over your app’s shape and behavior β€” at the type level.



Advanced TypeScript Types β€” A Deep, Practical Guide to keyof, typeof, infer, ReturnType, Parameters, and ConstructorParameters

Make your TypeScript code safer, more reusable, and interview-ready with these core advanced types. Includes real-world examples, common pitfalls, and patterns you’ll actually use.


TL;DR

  • keyof T β†’ union of property names of T
  • typeof expr β†’ turn a JS value/expression into a type
  • infer β†’ pull types out of other types inside conditional types
  • ReturnType<F> β†’ the function’s return type
  • Parameters<F> β†’ tuple of a function’s parameter types
  • ConstructorParameters<C> β†’ tuple of a class/constructor’s parameter types

Table of Contents

  1. What problem do these solve?
  2. keyof: Property name unions & object-safe APIs
  3. typeof: Type queries vs runtime typeof
  4. infer: Extracting types with conditional types
  5. ReturnType<T>
  6. Parameters<T> & variadic tuples
  7. ConstructorParameters<T>
  8. Putting it together: patterns you’ll actually use
  9. Edge cases & gotchas
  10. Quick cheat-sheet

1) Why these matter

Advanced type operators let you reflect on shapes, re-use existing types, and derive new ones automatically. This reduces duplication and prevents drift between concrete code and its types. If you’re building SDKs, form builders, routers, or design systems, these are your daily bread.


2) keyof: property names as a type

keyof T produces a union of property keys of T.

type User = {
  id: string;
  name: string;
  age?: number;
};

type UserKeys = keyof User; // "id" | "name" | "age"

Use cases

a) Key-safe getters/setters

function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const u: User = { id: "1", name: "Hari" };
const name = getProp(u, "name"); // string
// getProp(u, "email"); // ❌ Error: "email" not in User

b) Pick-by-keys with strong typing

function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const out = {} as Pick<T, K>;
  keys.forEach(k => (out[k] = obj[k]));
  return out;
}

const sub = pick(u, ["id", "name"]); // { id: string; name: string }

c) Mapped types + keyof for API DTOs

type Nullable<T> = { [K in keyof T]: T[K] | null };

type NullableUser = Nullable<User>;
/*
{
  id: string | null;
  name: string | null;
  age?: number | null;
}
*/

Tip: keyof is great for enforcing valid field names in search/sort/filter components.


3) typeof: type queries (not runtime typeof)

There are two typeofs:

  • Runtime typeof: JS operator β†’ "string" | "number" | ..."
  • Type query typeof: TS operator β†’ create a type from a value/expression
const config = {
  baseUrl: "/api",
  timeout: 5000,
  headers: { "X-Client": "web" },
} as const;

type Config = typeof config;
/*
{
  readonly baseUrl: "/api";
  readonly timeout: 5000;
  readonly headers: { readonly "X-Client": "web" };
}
*/

Use cases

a) Keep types in sync with constants

export const roles = ["admin", "editor", "viewer"] as const;
export type Role = typeof roles[number]; // "admin" | "editor" | "viewer"

b) Infer function types from actual functions

export function makeUser(id: string, name: string) {
  return { id, name, active: true as const };
}
export type MakeUser = typeof makeUser; // (id: string, name: string) => { ... }

Tip: Use as const to freeze literals and get literal types rather than widened types.


4) infer: pull types out of other types

infer only works inside conditional types (A extends B ? X : Y). It lets you name a type you’re extracting.

a) Extract array element type

type ElementOf<T> = T extends (infer U)[] ? U : never;

type A = ElementOf<string[]>;      // string
type B = ElementOf<number[][]>;    // number[]
type C = ElementOf<"x">;           // never

b) Extract promise inner type (awaited)

type Awaited<T> = T extends Promise<infer U> ? U : T;

type P1 = Awaited<Promise<number>>;   // number
type P2 = Awaited<string>;            // string

c) Extract function return/args (from scratch)

type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never;
type MyParameters<F> = F extends (...args: infer P) => any ? P : never;

d) Variadic tuple inference

type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : never;

type H = Head<[1, 2, 3]>; // 1
type T = Tail<[1, 2, 3]>; // [2, 3]

Mental model: infer is like destructuring types.


5) ReturnType<T>

Built-in utility: type ReturnType<T extends (...args: any) => any> = ...

function createSession(userId: string) {
  return { userId, token: crypto.randomUUID(), exp: Date.now() + 3600_000 };
}

type Session = ReturnType<typeof createSession>;

When to use

  • Keep reducer/action creators single source of truth
  • Generate slice/entity types from factory functions
  • Derive API response types from fetchers

6) Parameters<T> (and modern tuple magic)

Parameters<F> gives you a tuple of a function’s parameter types.

function logEvent(name: string, payload: { id: string }, at = new Date()) {}
type LogEventArgs = Parameters<typeof logEvent>; // [string, { id: string }, Date?]

Practical patterns

a) Forward arguments safely

function withTiming<F extends (...a: any[]) => any>(fn: F) {
  return (...args: Parameters<F>): ReturnType<F> => {
    const t0 = performance.now();
    const res = fn(...args);
    console.log("ms", performance.now() - t0);
    return res as ReturnType<F>;
  };
}

b) Event bus

type Handlers = {
  login: (userId: string) => void;
  click: (x: number, y: number) => void;
};

function emit<K extends keyof Handlers>(
  name: K,
  ...args: Parameters<Handlers[K]>
) {
  // ...
}
emit("click", 10, 20);  // βœ…

Note: Default parameters become optional in the resulting tuple (Date? above).


7) ConstructorParameters<T>

Gives the tuple of constructor args for a class/constructor-like.

class HttpClient {
  constructor(readonly baseUrl: string, readonly timeout: number = 5000) {}
}
type HttpArgs = ConstructorParameters<typeof HttpClient>; // [string, number?]

type HttpFactory = (...args: HttpArgs) => InstanceType<typeof HttpClient>;

Use cases

  • Build factories that mirror constructors
  • Abstract DI containers without duplicating types
  • Mock classes in tests by reusing constructor signatures

8) Putting it together: real patterns

Pattern 1: Schema-driven forms (no drift)

const profileSchema = {
  name: { label: "Name", required: true },
  age:  { label: "Age" },
} as const;

type Schema = typeof profileSchema;
type FieldName = keyof Schema; // "name" | "age"

type FieldConfig<N extends FieldName> = Schema[N];

function renderField<N extends FieldName>(name: N, cfg: FieldConfig<N>) {
  // ...
}
renderField("name", profileSchema.name); // fully typed

Pattern 2: Action creators with derived payloads

function makeAction<TType extends string>() {
  return <TPayload>(type: TType) =>
    (payload: TPayload) => ({ type, payload });
}

const setUser = makeAction()<"user/set">("user/set");
type SetUser = ReturnType<typeof setUser>; // { type: "user/set"; payload: ... }

Pattern 3: Typed middleware wrappers

function wrap<F extends (...a: any[]) => any>(fn: F) {
  return (...a: Parameters<F>): ReturnType<F> => fn(...a);
}

Pattern 4: Extracting API return model from a fetcher

async function fetchUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  return (await res.json()) as { id: string; name: string };
}
type UserDTO = ReturnType<typeof fetchUser> extends Promise<infer U> ? U : never;

Pattern 5: Router with route-safe params

const routes = {
  "/users/:id": (id: string) => `/users/${id}`,
  "/posts/:id/comments": (id: string) => `/posts/${id}/comments`,
} as const;

type Routes = typeof routes;
type RouteKey = keyof Routes;

function build<K extends RouteKey>(k: K, ...args: Parameters<Routes[K]>) {
  return routes[k](...args);
}

build("/users/:id", "42"); // βœ…


9) Edge cases & gotchas

  • Widening vs literal types: Without as const, typeof on objects/lists will widen ("admin" | "editor" | "viewer" becomes string). Use as const when you need exact literals.
  • Optional props with mapped types: Mapping over keyof preserves optionality unless you add -? or +?. type Requiredify<T> = { [K in keyof T]-?: T[K] }; // remove optionals
  • Distributive conditional types: T extends U ? X : Y distributes over unions. Wrap with [T] extends [U] to disable distribution.
  • any leaks: ReturnType<any> is any. Guard generics if you need stricter behavior.
  • Overloads: ReturnType<typeof overloaded> resolves to the last signature. Prefer explicit overload extraction if needed.

10) Quick cheat-sheet

// keyof
type Keys<T> = keyof T;

// typeof (type query)
const value = { a: 1 } as const;
type Value = typeof value; // { readonly a: 1 }

// infer (inside conditionals)
type Elem<T> = T extends (infer U)[] ? U : never;

// ReturnType
type R<F extends (...a: any[]) => any> = ReturnType<F>;

// Parameters
type P<F extends (...a: any[]) => any> = Parameters<F>;

// ConstructorParameters
type CP<C extends abstract new (...a: any) => any> = ConstructorParameters<C>;


Final Thoughts

Mastering these tools turns TypeScript into a meta-language for your app’s contracts. Start by:

  • Using typeof + as const for constants/configs
  • Guarding public helpers with <T, K extends keyof T>
  • Wrapping functions with Parameters/ReturnType when building utilities and middleware
  • Reaching for infer when you catch yourself parsing types in your head


TypeScript tsconfig.json β€” A Practical Deep Dive

What is tsconfig.json?

tsconfig.json tells the TypeScript compiler (tsc) how to type-check and emit your code. It sets the project root, file includes/excludes, and most importantly, compilerOptions.

Create one quickly:

npx tsc --init


The Essentials First

strict

What it does: Turns on TypeScript’s full safety mode by enabling several checks at once (noImplicitAny, strictNullChecks, strictBindCallApply, etc.).

Recommendation: Always true for production code.

{
  "compilerOptions": {
    "strict": true
  }
}


noImplicitAny

What it does: Disallows variables/parameters from silently becoming any.

Why it matters: Prevents β€œtype leaks” that hide bugs.

{
  "compilerOptions": {
    "noImplicitAny": true
  }
}

If strict is true, you already get this.


strictNullChecks

What it does: Makes null/undefined explicit in types β€” you must handle them.

Why it matters: Eliminates a big class of runtime errors.

{
  "compilerOptions": {
    "strictNullChecks": true
  }
}

Also included when strict: true.


Module & Language Targets

target

What it does: Chooses the JS version emitted by tsc.

Guidelines:

  • Node 18+ / modern bundlers: ES2022 or ES2020
  • Legacy browsers: ES2017 (with polyfills)
{
  "compilerOptions": {
    "target": "ES2022"
  }
}

module

What it does: Sets the output module system.

Guidelines:

  • Node (ESM): "NodeNext"
  • Node (CommonJS): "CommonJS"
  • Frontend (bundlers like Vite/Next): "ESNext"
{
  "compilerOptions": {
    "module": "ESNext"
  }
}

lib

What it does: Declares the built-in APIs available during type-checking (e.g., DOM, ES2021).

Guidelines:

  • Browser app: include DOM.
  • Node app: omit DOM.
{
  "compilerOptions": {
    "lib": ["ES2022", "DOM"]
  }
}


Interop & Imports

esModuleInterop

What it does: Smooths default import behavior with CommonJS modules.

Use it when: Importing CJS libs with import x from 'lib' syntax.

{
  "compilerOptions": {
    "esModuleInterop": true
  }
}

Also consider "allowSyntheticDefaultImports": true for type-checking only (no emit change). Most projects enable both.


Clean Imports with baseUrl & paths

baseUrl

What it does: Sets the root for non-relative imports.

{
  "compilerOptions": {
    "baseUrl": "src"
  }
}

Now you can write:

import { api } from "services/api";

instead of ../../services/api.

paths

What it does: Adds aliases for folders/modules.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"]
    }
  }
}

Important: TypeScript only type-checks these paths. Your runtime must also understand them:

  • Vite: set resolve.alias in vite.config.ts
  • Next.js: add compilerOptions.paths (Next handles baseUrl/paths automatically when using @/* via jsconfig/tsconfig) or use next.config.mjs aliases when needed
  • Node: use a bundler or tsconfig-paths with ts-node
  • Jest: mirror aliases in moduleNameMapper

Recommended Starter Configs

1) Modern React (Vite/Next)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022", "DOM"],
    "jsx": "react-jsx",

    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,

    "baseUrl": ".",
    "paths": { "@/*": ["src/*"] },

    "moduleResolution": "Bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,

    "skipLibCheck": true
  },
  "include": ["src"]
}

2) Node 18+ (ESM with .ts/.mts)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "lib": ["ES2022"],

    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,

    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,

    "outDir": "dist",
    "skipLibCheck": true
  },
  "include": ["src"]
}

3) Library Template (emit clean ES + types)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM"],
    "declaration": true,
    "declarationMap": true,
    "emitDeclarationOnly": false,

    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,

    "moduleResolution": "Bundler",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,

    "outDir": "dist",
    "skipLibCheck": true,
    "sourceMap": true
  },
  "include": ["src"]
}


Common Gotchas (and Quick Fixes)

  • Aliases work in tsc but fail at runtime
    Set matching aliases in your bundler/test runner. For Node+ts-node: install tsconfig-paths and run with ts-node -r tsconfig-paths/register.
  • Mixing ESM/CJS in Node
    Choose one:
    • ESM: "module": "NodeNext" and set "type": "module" in package.json.
    • CJS: "module": "CommonJS" and omit "type": "module".
  • DOM types in Node environments
    Remove "DOM" from lib when building backend services to avoid incorrect type availability.
  • Slow type-checking
    Use "skipLibCheck": true (safe for most projects). Keep "incremental": true to speed up subsequent builds.

Pro Tips

  • Split configs with "extends" for cleaner setups:
// tsconfig.base.json
{
  "compilerOptions": {
    "strict": true,
    "skipLibCheck": true
  }
}

// tsconfig.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": { "lib": ["ES2022", "DOM"] },
  "include": ["src"]
}

  • For test files, add a tsconfig.test.json with DOM or Node globals as needed.
  • Keep strict on; if you must relax, do it locally with // @ts-ignore sparingly or narrow specific options, not the whole project.