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 ] ]