TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
25: Union and Intersection Types
I've been working on a notification system for a client, and I ran into a classic TypeScript architectural headache. The system needs to handle different types of messages—some are just emails, some are just SMS texts, and a few are "Critical Alerts" that actually require both. Let's walk through how I solved this, because it's the perfect way to see where Union and Intersection types actually fit in.
The "Either-Or" Dilemma
First, I defined my basic data structures. Pretty straightforward stuff.
type Email = {
emailAddress: string;
subject: string;
};
type SMS = {
phoneNumber: string;
message: string;
};
Now, I wanted a function that could process a notification regardless of whether it was an email or an SMS. My first instinct was to just pick one, but that doesn't make sense. I need it to be flexible. So, I tried this:
function sendNotification(config: Email | SMS) {
console.log("Sending notification...");
// Let's try to log the destination
console.log(config.emailAddress);
}
Immediately, TypeScript flags config.emailAddress with a red squiggly line. It tells me that emailAddress doesn't exist on type SMS. Which makes sense! If I pass an SMS object into this function, there is no email address to log.
This is the core of a Union Type (the | symbol). I'm telling TypeScript: "This variable could be an Email OR it could be an SMS." The catch is that TypeScript will only let me access properties that are common to both types. Since emailAddress and phoneNumber are unique to their respective types, I can't just grab them blindly.
Narrowing the Path
To fix this, I can't just tell TypeScript to "trust me." I have to prove which type I'm dealing with. I'll use a check to narrow the type down.
function sendNotification(config: Email | SMS) {
if ('emailAddress' in config) {
// Inside this block, TS knows config is an Email
console.log(`Sending email to ${config.emailAddress}`);
} else {
// Since it's not an Email, it must be an SMS
console.log(`Sending SMS to ${config.phoneNumber}`);
}
}
That works. By using the in operator, I've narrowed the Union. It's a common pattern: start broad with a Union, then use conditional logic to get specific.
When "Or" Isn't Enough
Then the requirements changed. The client wanted "Critical Alerts." These aren't just emails or SMS; they are a hybrid. A critical alert must have the properties of an Email AND the properties of an SMS to ensure the user is reached by all available means.
I could create a third type and manually copy all the fields, but that's a maintenance nightmare. If I add a cc field to Email later, I'd have to remember to add it to CriticalAlert too. Instead, I'll try an Intersection Type (the & symbol).
type CriticalAlert = Email & SMS;
const emergency: CriticalAlert = {
emailAddress: "admin@system.com",
subject: "SERVER DOWN",
phoneNumber: "555-0199",
message: "Immediate action required!"
};
Notice the difference. While the Union (|) was an "Either/Or" situation, the Intersection (&) is an "And" situation. A CriticalAlert is now a type that possesses every single property of Email AND every single property of SMS. If I forgot to include the phoneNumber in the emergency object, TypeScript would throw an error immediately.
Choosing the Right Tool
It's easy to mix these up when you're staring at code at 2 AM. Here is how I keep them straight in my head:
- Unions (
|): Use these when a value can be one of several different things. It expands the possibilities of what the value can be, but restricts what properties you can access until you narrow the type. - Intersections (
&): Use these when you want to combine multiple types into one. It restricts the possibilities (the object must satisfy all criteria) but expands the properties available to you.
📋 Practical Task
Building a User Permission System
You are building a permission system for a CMS. You have two base types: Guest and Admin.
type Guest = {
canView: boolean;
};
type Admin = {
canEdit: boolean;
canDelete: boolean;
};
Your task is to implement the following:
- Create a Union type called
Userthat can be either aGuestor anAdmin. - Create an Intersection type called
SuperUserthat combinesGuestandAdmin. - Write a function called
checkPermissionsthat accepts aUser. Inside the function, use a type guard (like theinoperator) to log "Admin access granted" if the user is an Admin, and "Guest access granted" otherwise. - Create a
SuperUserobject and verify that you can accesscanView,canEdit, andcanDeleteon it without any TypeScript errors.
There are no comments for now.