π§βπ Beginner Level
β 1. TypeScript Basics
- What is TypeScript? Why use it?
- Installing TypeScript (
tsc, tsconfig.json) - Compilation:
.tsto.js
β 2. Basic Types
string,number,booleanany,unknown,void,null,undefinedlet,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 | numbertype B = A & { extra: boolean }
β 8. Type Assertions & Guards
askeywordtypeof,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,inferReturnType<T>,Parameters<T>,ConstructorParameters<T>
π§° Tooling & Ecosystem
β 16. tsconfig.json Deep Dive
strict,noImplicitAny,strictNullCheckspaths,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?
| Feature | JavaScript | TypeScript |
|---|---|---|
| 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)
| Version | Release Date | Major Features |
|---|---|---|
| 0.8 | Oct 2012 | First public release |
| 1.0 | Apr 2014 | First stable release |
| 2.0 | Sep 2016 | readonly, never, non-nullable types |
| 3.0 | Jul 2018 | Tuples with optional/rest elements, project references |
| 4.0 | Aug 2020 | Variadic tuple types, labeled tuple elements |
| 4.1 | Nov 2020 | Template literal types |
| 4.4 | Aug 2021 | Control flow analysis of aliased conditions |
| 4.9 | Nov 2022 | satisfies operator |
| 5.0 | Mar 2023 | Decorators, const type params, improved speed |
| 5.4 | Feb 2024 | Improved type narrowing, type predicates |
| 5.5 | Jul 2024 | Inferred 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:
- Node.js β Runtime to install and use the TypeScript compiler.
- TypeScript Compiler (
tsc) β Converts.tsfiles into.js. - 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?
- π§ͺ Inspect how TypeScript parses your code
- π Debug type errors at a deeper level
- π¦ Learn how custom tools (like ESLint, Babel) analyze code
- π§° 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:
FunctionDeclarationParameterStringLiteralReturnStatement
Each node shows how the compiler breaks down your code.
π Summary
| Tool | Purpose |
|---|---|
| AST Explorer | Visualizes TypeScript AST |
| Use Cases | Debugging, tool-building, code insight |
| Try It Here | ts-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
| Type | Example |
|---|---|
| string | "Hari" |
| number | 42, 3.14 |
| boolean | true, false |
| null | null |
| undefined | undefined |
| bigint | 123456789n |
| symbol | Symbol("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
| Type | Example |
|---|---|
| object | { name: "Hari" } |
| array | [1, 2, 3] |
| tuple | ["Hari", 30] |
| function | (a: number, b: number) => {} |
| class | class Car {} |
| interface | interface User {} |
| enum | enum Direction {} |
| any | any |
| unknown | unknown |
| void | void |
| never | never |
π§ 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.14booleanβtrue,falsenull,undefinedbigint,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 | numberintersection typesβAdmin & Userliteral typesβ"GET" | "POST"mapped typesβPartial<T>,Readonly<T>
β Best Practices
- Use
let&const(notvar) - Avoid
any(preferunknownor 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 astringuser[1]is always anumber
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?
| Feature | Array | Tuple |
|---|---|---|
| Size | Dynamic | Fixed |
| Type | Same for all elements | Can vary per index |
| Use Case | Lists, collections | Struct-like data (e.g., [name, age]) |
| Access | Indexed generically | Indexed 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 stringperson[1]is always a number- Changing the type or order will throw an error
π Tuple vs Array: What’s the Difference?
| Feature | Tuple | Array |
|---|---|---|
| Length | Fixed | Dynamic |
| Types | Can vary per element | All elements must be same type |
| Use Case | Structured data (name, age) | Lists of same-type items |
| Type Safety | Enforced per index | Uniform 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
| Method | Description | Use 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
strictTupleTypesintsconfig.jsonto prevent unsafe mutations.
π‘ Best Practices for Tuples
- β Use when the structure is known and limited
- β Donβt treat tuples like generic arrays
- β
Use
readonlywhen you want immutable tuples - β 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
strictTupleTypesensures 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?
| Feature | Array | Tuple |
|---|---|---|
| Length | Dynamic | Fixed |
| Types | All elements have same type | Each element can have different type |
| Methods | Fully supported | Supported but can break safety |
π§° Array & Tuple Methods β Categorized
π 1. Iteration & Transformation
| Method | Description | Works on Tuple? | Example |
|---|---|---|---|
forEach() | Iterate over each element | β | arr.forEach(v => ...) |
map() | Transform each element | β οΈ Use carefully | arr.map(v => v * 2) |
filter() | Filter items based on condition | β οΈ Loses tuple types | arr.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
| Method | Description | Works on Tuple? | Example |
|---|---|---|---|
push() | Add item to end | β οΈ Unsafe | arr.push(99) |
pop() | Remove last item | β οΈ Unsafe | arr.pop() |
shift() | Remove first item | β οΈ Unsafe | arr.shift() |
unshift() | Add item at start | β οΈ Unsafe | arr.unshift(1) |
splice() | Add/remove at specific index | β οΈ Unsafe | arr.splice(1, 1) |
fill() | Replace all values | β οΈ Dangerous | arr.fill(0) |
copyWithin() | Copy elements to another index | β οΈ Dangerous | arr.copyWithin(0, 1) |
reverse() | Reverse in place | β οΈ Dangerous | arr.reverse() |
sort() | Sort in place | β οΈ Dangerous | arr.sort() |
π Tuples are meant to be fixed-size. Mutation methods should be avoided unless using rest elements. Use
strictTupleTypesintsconfig.json.
π§ͺ 3. Search & Check Methods
| Method | Description | Works 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
| Method | Description | Works on Tuple? | Example |
|---|---|---|---|
slice() | Return a shallow copy | β οΈ Tuple becomes array | arr.slice(0, 2) |
concat() | Merge arrays | β οΈ Unsafe | arr.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
| Feature | Description | Example |
|---|---|---|
| Labeled Tuples | Label positions in tuple for clarity | type Point = [x: number, y: number] |
| Optional Elements | Allow missing elements in tuple | let t: [number, number?] = [10]; |
| Rest Tuples | Variable length from specific position | let log: [string, ...string[]] |
π Best Practices for Tuples
- β
Use for fixed, known structures (
[name, age]) - β Avoid unsafe mutations (
push,pop) on plain tuples - β
Enable
strictTupleTypesfor safer code - β
Use
readonlyto make tuples immutable - β 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?
| Feature | Enums | Literal Types |
|---|---|---|
| Runtime presence | Yes | No (type-only) |
| Reverse lookup | Only for numeric enums | No |
| Code readability | Better for grouped constants | Cleaner for short values |
| Use in APIs/logging | String enums recommended | Literal types work great |
| Extendability | Static only | Can be extended with union |
| Bundle size | Adds JS output | No 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 parameternamemust 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?: stringmakes thenameparameter 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
addis 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:
| Feature | Purpose |
|---|---|
| Return type | Enforces expected output |
| Parameter typing | Prevents unexpected arguments |
| Optional/default | Adds flexibility while keeping types intact |
| Function types | Reusable and predictable signatures |
| Interface-based functions | Useful 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
typeandinterface - β 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
| Feature | type | interface |
|---|---|---|
| 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
interfacewhen:- Youβre defining object models or contracts.
- You need declaration merging (e.g., plugin ecosystems).
- You work in class-based code.
- β
Use
typewhen:- 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
interfaceis best for object-oriented and extensible design.typeis 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:
Ais a union type (string | number)Bis an intersection betweenAand an object type{ extra: boolean }
However,
Bis never in this example becausestring | numbercannot 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:
Entityis a union type (Admin | User)WithMetais each of those plus acreatedAtfield
β When to Use
| Use Case | Use |
|---|---|
| Multiple options (e.g. string or number) | `Union ( |
| Require multiple structures together | Intersection (&) |
π§ 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
- typeof
- instanceof
- 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?
| Feature | Type Assertion | Type Guard |
|---|---|---|
| Timing | Compile-time only | Runtime |
| Safety | Risky β Trusts the developer | Safe β Evaluates type before use |
| Purpose | Tells TypeScript the type | Narrows type based on condition |
| Runtime Behavior | No effect | Affects 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
| Feature | Use Case |
|---|---|
public | Accessible everywhere |
private | Hidden from outside |
protected | Visible to class + subclasses |
constructor | Initialize instance properties |
extends | Inherit from a base class |
implements | Enforce class structure via interfaces |
abstract | Create base blueprint classes |
static | Class-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
| Modifier | Accessible From |
|---|---|
public | Anywhere |
private | Only inside the declaring class |
protected | Class + 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
| Feature | Purpose |
|---|---|
| Mixins | Reuse behavior across classes |
| Generics | Type-safe flexible classes |
| Design Patterns | Standard 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
| Pattern | Use Case | Benefit |
|---|---|---|
| Decorator | Add new behavior to existing code | No need to change original class |
| Strategy | Swap algorithms or logic dynamically | Clean, pluggable logic |
| Command | Encapsulate actions or events | Undo/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
| Pattern | Use Case | Key Benefit |
|---|---|---|
| Observer | Direct subscription | Tight coupling |
| Pub/Sub | Event-driven communication | Loose coupling |
| State | Change behavior based on state | Cleaner conditional logic |
| Mediator | Central communication hub | Reduces inter-object dependency |
| Builder | Step-by-step object construction | Customizable instantiation |
| Adapter | Wrap incompatible APIs | Ensures 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
| Concept | Where Used |
|---|---|
| Interfaces | IOrderRepository, Order, DTOs |
| Abstraction Layer | Service, Repository |
| Dependency Injection | Manual in app.ts |
| Separation of Concerns | Clean folder structure |
| DTOs | Validated 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
.envwith 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:
- β
.envfor 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
| Tool | Purpose |
|---|---|
.env | Manages secrets, DB URLs, ports |
Docker | Containerizes the app |
Docker Compose | Runs MongoDB with app in dev/prod |
GitHub Actions | CI/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
- β¨
importandexportsyntax - βοΈ TypeScript compiler flags:
esModuleInteropandallowSyntheticDefaultImports - 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
| Feature | Description | Default? |
|---|---|---|
export | Makes functions/classes/constants available to other files | β |
default export | Exports a single main value | β |
esModuleInterop | Enables default import from CommonJS | β |
allowSyntheticDefaultImports | Allows 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
Tis 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
| Utility | What it Does | Best For |
|---|---|---|
Partial<T> | Makes all properties optional | PATCH APIs |
Required<T> | Makes all properties required | Config enforcement |
Readonly<T> | Freezes properties | Immutable state |
Pick<T,K> | Select specific fields | Public views |
Omit<T,K> | Remove selected fields | Hide sensitive data |
Record<K,T> | Create typed object maps | Config, role-permission map |
Exclude<T,U> | Remove union members | Filter specific types |
Extract<T,U> | Keep matching union members | Get common subset |
NonNullable<T> | Remove null/undefined | Safer 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
- What Are Type Manipulations?
- π Conditional Types
- π Mapped Types
- π€ Template Literal Types
- π― Bonus: Key Remapping
- π Use Cases in Real Projects
- π 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
| Technique | Use Case |
|---|---|
Conditional | Type-based branching logic (API response handler, etc.) |
Mapped | Transforming DTOs, response shaping |
Template | Event names, key builders, tag/slug formats |
Remapping | Rename fields, exclude/include certain fields dynamically |
π Save this blog
π¬ Have a favorite use case? Share it below!
β¨ Related Topics:
- Utility Types (
Pick,Omit,Extract) inferkeyword 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
kindenables TypeScript to narrow down the exact shape variant - Helps you avoid manual type assertions or unsafe logic
π οΈ Real-World Use Cases
| Feature | Use Case |
|---|---|
Pick / Omit | Form validation, public vs private types |
Extract | Filter roles, permissions, enums |
infer | Build reusable type helpers (e.g. ExtractParamType) |
| Discriminated Unions | Redux 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:
- Advanced Mapped Types β with modifiers, filtering, and remapping
- Template Literal Types β types as dynamic string patterns
- 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 keysOmit<T, K>: Remove some keysRecord<K, T>: Create new objects from union keysPartial<T>: Make everything optionalReadonly<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 responseComponentProps<T>β generic prop injectionFormErrors<T>β partial error types for each fieldEventMap<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 ofTtypeof exprβ turn a JS value/expression into a typeinferβ pull types out of other types inside conditional typesReturnType<F>β the functionβs return typeParameters<F>β tuple of a functionβs parameter typesConstructorParameters<C>β tuple of a class/constructorβs parameter types
Table of Contents
- What problem do these solve?
keyof: Property name unions & object-safe APIstypeof: Type queries vs runtimetypeofinfer: Extracting types with conditional typesReturnType<T>Parameters<T>& variadic tuplesConstructorParameters<T>- Putting it together: patterns youβll actually use
- Edge cases & gotchas
- 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:
keyofis 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 constto 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:
inferis 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,typeofon objects/lists will widen ("admin" | "editor" | "viewer"becomesstring). Useas constwhen you need exact literals. - Optional props with mapped types: Mapping over
keyofpreserves optionality unless you add-?or+?.type Requiredify<T> = { [K in keyof T]-?: T[K] }; // remove optionals - Distributive conditional types:
T extends U ? X : Ydistributes over unions. Wrap with[T] extends [U]to disable distribution. anyleaks:ReturnType<any>isany. 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 constfor constants/configs - Guarding public helpers with
<T, K extends keyof T> - Wrapping functions with
Parameters/ReturnTypewhen building utilities and middleware - Reaching for
inferwhen 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
strictistrue, 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:
ES2022orES2020 - 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": truefor 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.aliasinvite.config.ts - Next.js: add
compilerOptions.paths(Next handlesbaseUrl/pathsautomatically when using@/*viajsconfig/tsconfig) or usenext.config.mjsaliases when needed - Node: use a bundler or
tsconfig-pathswithts-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: installtsconfig-pathsand run withts-node -r tsconfig-paths/register. - Mixing ESM/CJS in Node
Choose one:- ESM:
"module": "NodeNext"and set"type": "module"inpackage.json. - CJS:
"module": "CommonJS"and omit"type": "module".
- ESM:
- DOM types in Node environments
Remove"DOM"fromlibwhen building backend services to avoid incorrect type availability. - Slow type-checking
Use"skipLibCheck": true(safe for most projects). Keep"incremental": trueto 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.jsonwith DOM or Node globals as needed. - Keep
stricton; if you must relax, do it locally with// @ts-ignoresparingly or narrow specific options, not the whole project.