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
26: Literal Types
I'm currently working on a small music player component, and I've run into a classic problem. I have a function that updates the UI based on the player's current state. Initially, I did what most of us do: I used a string for the state.
function updatePlayerUI(state: string) {
if (state === "playing") {
console.log("Show the pause button");
} else if (state === "paused") {
console.log("Show the play button");
} else if (state === "stopped") {
console.log("Show the stop button");
}
}
updatePlayerUI("playing"); // Works great.
updatePlayerUI("paused"); // Still great.
The "Anything Goes" Problem
Here is the issue. Because I told TypeScript that state is a string, TypeScript trusts me implicitly. It thinks any sequence of characters is valid. I tried this just now, and look what happens:
updatePlayerUI("banana");
The code compiles. No red squiggly lines. But when I run it, the function does absolutely nothing because "banana" doesn't match any of my if statements. In a real app, this is how silent bugs creep in—you pass a typo like "palying" and spend an hour wondering why the UI isn't updating.
Tightening the Screws
I want TypeScript to scream at me the moment I type something that isn't one of my three valid states. I don't want any string; I want these specific strings. Let's try changing the type definition to use the exact values.
function updatePlayerUI(state: "playing" | "paused" | "stopped") {
// ... logic remains the same
}
updatePlayerUI("playing"); // This is fine.
updatePlayerUI("banana"); // Error: Argument of type '"banana"' is not assignable to parameter of type '"playing" | "paused" | "stopped"'.
Now we're talking. By using the actual values—"playing", "paused", and "stopped"—instead of the general string type, I've created Literal Types. I'm telling the compiler: "The only valid values for this variable are these exact literals."
It's Not Just for Strings
I wondered if this worked for other types, so I tried it with a priority system for a notification queue. I only want priorities to be 1 (High), 2 (Medium), or 3 (Low). I don't want someone passing in 99 or -1.
function setPriority(level: 1 | 2 | 3) {
console.log(`Priority set to ${level}`);
}
setPriority(1); // Sweet.
setPriority(5); // Error: Argument of type '5' is not assignable to parameter of type '1 | 2 | 3'.
It works exactly the same way for numbers and booleans. It basically turns a value into a type. I'll admit, writing 1 | 2 | 3 inside a function signature feels a bit clunky if I have to do it in five different places in my code.
Making it Scale
To clean this up, I'll move these literals into a type alias. This makes the code more readable and gives me a single place to add a new state (like "buffering") without hunting through every function signature in my project.
type PlayerState = "playing" | "paused" | "stopped" | "buffering";
function updatePlayerUI(state: PlayerState) {
// Now it's clean and reusable
}
const currentState: PlayerState = "playing";
One last thing to notice: if you hover over currentState in your editor, you'll see that TypeScript knows exactly which options you have. The autocomplete becomes a superpower here because you aren't guessing what strings the API expects—the IDE just tells you.
📋 Practical Task
Building a State-Driven Notification System
You are building a notification system for a dashboard. The system should only allow three specific severity levels: "info", "warning", and "error". It should also only allow three specific delivery methods: "email", "sms", and "push".
Your Task:
- Create a type alias called
Severitythat only allows the three severity literals. - Create a type alias called
DeliveryMethodthat only allows the three delivery literals. - Define an interface
Notificationthat uses these two types for its properties. - Create a function
sendNotification(note: Notification)that logs a message to the console. - Try to create a notification object with an invalid severity (e.g.,
"critical") and observe the TypeScript error.
There are no comments for now.