Skip to main content

Advanced Type Combinations & Modifiers

Unions

A union type allows a variable to hold values of multiple types, using the pipe | operator.

let tax: number | string = 10;
tax = "$10"; // also valid

Union types are especially useful for:

  • API responses that return different shapes
  • User input that could be text or a number
type RequestStatus = 'pending' | 'success' | 'error';

let status: RequestStatus = 'pending'; // yes
let status2: RequestStatus = 'loading'; // Error

When working with union types, you may need type guards (like typeof checks) to handle each case properly.

Intersections

Intersection types use & — a variable must satisfy both types simultaneously.

type A = { name: string };
type B = { age: number };

type Person = A & B;

const p: Person = { name: "Ajay", age: 23 }; // must have both

readonly

Use readonly to make a property immutable after it's set.

type Point = {
readonly x: number;
readonly y: number;
};

const p: Point = { x: 10, y: 20 };
p.x = 5; // Error: x is read-only

Useful for config objects, constants, and ensuring data integrity.

keyof

keyof extracts the union of keys from a type — great for safe dynamic property access.

type Person = {
name: string;
age: number;
isAdult?: boolean;
};

function getValue(key: keyof Person, person: Person) {
return person[key];
}

const val = getValue('name', { name: 'ajay', age: 23 });
console.log(val); // 'ajay'

keyof ensures you can only pass valid property names. Passing 'email' would throw a compile-time error.

typeof

📝 Notes to be filled while learning

Index Types

Use bracket notation to pull out the type of a specific property from another type.

Example 1 — Literal property type

type Person = {
name: string;
skillLevel: "Beginner" | "Intermediate" | "Expert";
};

const person: Person = { name: 'Ajay', skillLevel: 'Expert' };

function printSkillLevel(skillLevel: Person['skillLevel']) {
console.log(skillLevel);
}

printSkillLevel(person.skillLevel); // yes
printSkillLevel('Unknown'); // Error

Example 2 — Dynamic object value types

const person = {
name: 'ajay',
age: 23,
};

// Gets the type of all values: string | number
type PersonValues = (typeof person)[keyof typeof person];

as const and Enums

as const

as const locks a value to its most specific literal type and makes everything readonly.

let a = 1 as const;  // type is literally `1`, not `number`
const b = 1 as const;

const SKILL_LEVELS = ['Beginner', 'Intermediate', 'Expert'] as const;
// type is: readonly ["Beginner", "Intermediate", "Expert"]

type Person = {
name: string;
skillLevel: (typeof SKILL_LEVELS)[number]; // "Beginner" | "Intermediate" | "Expert"
};

SKILL_LEVELS.forEach((skillLevel) => {
console.log(skillLevel);
});

(typeof SKILL_LEVELS)[number] is a neat pattern to derive a union type from an as const array.

Enums

Enums let you define a set of named constants.

enum Direction {
Up,
Down,
Left,
Right,
}

let move: Direction = Direction.Up;

You can also use string enums:

enum Status {
Pending = 'PENDING',
Success = 'SUCCESS',
Error = 'ERROR',
}

For simple string unions, prefer as const arrays or literal types over enums — they're lighter and more composable.

Tuples

A tuple is a fixed-length array where each position has a specific type.

type Tuple = [string, number];

const a: Tuple = ['ajay', 23]; // yes
const b: Tuple = [23, 'ajay']; // Error: order matters

Real-world use — Object.entries

const person = {
name: 'ajay',
age: 23,
};

// Object.entries returns [string, any][] — tuples under the hood
Object.entries(person).forEach(([key, val]) => {
console.log(key, val);
});

console.log(Object.entries(person));
// [ [ 'name', 'ajay' ], [ 'age', 23 ] ]