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
177: Type Aliases vs Interfaces Revisited
If you've been hanging around TypeScript forums or reading a few blog posts, you've probably encountered the common advice that "Interfaces and Type Aliases are functionally identical for 95% of use cases." I'll be honest: for a beginner, that's a helpful simplification. But as you move into senior-level architecture, treating them as interchangeable is a mistake that will eventually bite you.
The "They're Basically Identical" Myth
The misconception is that the only difference between type and interface is the syntax. People assume that if they can describe an object shape with either, it doesn't matter which one they pick. Let me show you why that's wrong by looking at how TypeScript handles "open" vs "closed" definitions.
// Using a Type Alias
type User = {
name: string;
email: string;
};
// This will trigger a TS error: "Duplicate identifier 'User'."
type User = {
phoneNumber: string;
};
With a type alias, once it's defined, it's locked. You can't just "add" more properties to it later in the same namespace. Now, look at what happens when we use an interface.
// Using an Interface
interface User {
name: string;
email: string;
}
// This is perfectly legal! This is called "Declaration Merging."
interface User {
phoneNumber: string;
}
const me: User = {
name: "Alex",
email: "alex@dev.com",
phoneNumber: "555-0123"
};
Why Declaration Merging Changes the Game
You might be thinking, "Why on earth would I want my types to be accidentally overwritten?" In your internal application code, you probably don't. But if you're writing a library or a shared SDK, declaration merging is a superpower. It allows the consumer of your library to extend your types without having to wrap everything in a new interface.
I've spent hours debugging weird bugs in third-party plugins where the plugin author used type for everything, making it impossible for me to add a custom property to the configuration object without a massive refactor. If you're building a public-facing API, use interfaces for your main object shapes. If you're building a private component and want to ensure the shape remains exactly as defined, a type alias is your best friend.
When Type Aliases Actually Outshine Interfaces
While interfaces win on extensibility, they are fundamentally limited to describing object shapes. Type aliases are far more flexible because they can represent anything. You cannot use an interface to define a union type, which is one of the most powerful features of TypeScript.
// This is impossible with an interface
type Status = 'pending' | 'approved' | 'rejected';
type Response = {
data: string;
} | {
error: string;
code: number;
};
If you tried to force the Response example into an interface, you'd end up with a mess of optional properties (data? and error?), which destroys your type safety. With a union type alias, TypeScript forces you to check which "branch" of the union you're dealing with before you access the properties. I almost always use type for state management and API responses for exactly this reason.
📋 Practical Task
Refactoring the Payment Gateway Type System
You are working on a payment processing module. Currently, the code uses interfaces for everything, which is causing two problems: you can't represent the "Payment Method" as a choice between options, and the PaymentConfig is too rigid for users of your module to extend.
Your Goal: Refactor the following code to use the correct tool for the job.
- Change
PaymentMethodto a type alias that is a union of'credit_card' | 'paypal' | 'crypto'. - Ensure
PaymentConfigremains an interface so that other developers can use declaration merging to add their own custom metadata to the config. - Create a type alias called
PaymentResultthat is a union of aSuccessobject (with atransactionId) or aFailureobject (with anerrorCode).
// REFACTOR THIS CODE
interface PaymentMethod {
// Currently just a string, make this a union type alias!
value: string;
}
interface PaymentConfig {
apiKey: string;
timeout: number;
}
// Create a PaymentResult type alias here as a union of Success or Failure
function processPayment(method: PaymentMethod, config: PaymentConfig): any {
// Implementation logic...
return { transactionId: "TXN_123" };
}
There are no comments for now.