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
32: Template Literal Types
Imagine you're managing a small coffee shop. You have a specific list of sizes (Small, Medium, Large) and a specific list of roast types (Light, Medium, Dark). Instead of just letting people order "whatever," you've decided that every internal order ticket must follow a strict format: Size-Roast. So, a ticket might say Small-Light or Large-Dark.
In your head, you've just created a rule. You didn't just say "the ticket is a string"; you said "the ticket is a combination of one of these three sizes and one of these three roasts." Template Literal Types in TypeScript let us do exactly that with our type system. We aren't just defining a string; we're defining a pattern that strings must follow.
The Magic of String Interpolation for Types
You're already used to template literals in JavaScript for creating strings at runtime. TypeScript brings that same syntax to the type level. I find this incredibly useful when I'm dealing with things like event names or API endpoints where the naming convention is strict.
Let's look at a real-world scenario: a notification system. You might have different categories of notifications and different severity levels. Instead of typing out every single possible combination manually, we can let TypeScript do the math for us:
type Category = "user" | "system" | "billing";
type Severity = "info" | "warning" | "error";
// This is where the magic happens
type NotificationEvent = `${Category}_${Severity}`;
// Valid:
const event1: NotificationEvent = "user_info";
const event2: NotificationEvent = "billing_error";
// Error: "user_critical" (critical isn't in Severity)
// Error: "admin_info" (admin isn't in Category)
const event3: NotificationEvent = "user_critical";
Notice how TypeScript automatically expands NotificationEvent to be a union of all 9 possible combinations. If you hover over the type in your IDE, you'll see the full list. It's a huge time-saver and prevents those annoying typos that usually only surface as bugs in production.
Combining Types to Build a Matrix
The real power kicks in when you start nesting these or combining them with other literal types. I often use this when building UI components that have "variants."
Suppose you're building a set of CSS-like utility classes for a design system. You have directions (top, bottom, left, right) and spacing scales (1, 2, 4, 8). You can create a type that ensures you only use valid spacing classes:
type Direction = "top" | "bottom" | "left" | "right";
type Scale = 1 | 2 | 4 | 8;
type SpacingClass = `m-${Direction}-${Scale}`;
const myMargin: SpacingClass = "m-top-4"; // Perfectly valid
const badMargin: SpacingClass = "m-center-4"; // Error! "center" isn't a Direction
I love this because it turns the compiler into a design system enforcer. You don't have to memorize the documentation; the autocomplete literally tells you what's allowed.
Taming the Chaos with Constraints
A word of caution: be careful not to overdo it. If you combine five different unions with ten members each, you'll end up with a type that has 100,000 possible permutations. TypeScript can actually start to lag or throw a "type instantiation is excessively deep" error if the combinations explode too quickly.
If you find yourself hitting that wall, it's usually a sign that your type system is trying to do too much of the logic that should probably just be a simple string check at runtime. Use template literal types for patterns that are small enough to be manageable but strict enough to be useful.
📋 Practical Task
Exercise: Building a Type-Safe API Endpoint Generator
You are building a client for a REST API. The API follows a strict naming convention for its endpoints: it always starts with api/, followed by a resource name, and ends with a specific action.
Your task: Create a type-safe system to ensure endpoints are constructed correctly.
- Define a type
Resourcethat can be"users","posts", or"comments". - Define a type
Actionthat can be"get","create","update", or"delete". - Create a Template Literal Type called
ApiEndpointthat forces the format:api/[Resource]/[Action]. - Declare a variable
currentRouteof typeApiEndpointand assign it a valid path (e.g.,"api/users/get"). - Try to assign an invalid path (like
"api/products/get"or"api/users/fetch") to see the TypeScript compiler catch the error.
// Start your code here:
type Resource = // ...
type Action = // ...
type ApiEndpoint = // ...
const currentRoute: ApiEndpoint = // ...
There are no comments for now.