Think of it like building a web platform. Let's use a real-world scenario: you are building an E-commerce Dashboard.
To never forget the difference, memorize these two personas:
- Interfaces are Open Blueprints (Infrastructure). They are meant for objects and they are "open for extension."
- Types are Strict Formulas (Transformations). They can be anything (objects, strings, unions), but once defined, they are locked.
Here is the exact breakdown of how they behave differently in the real world.
Scenario 1: The Third-Party Analytics Script (Why you NEED interface)
Imagine you are writing a script that tracks user clicks on this E-commerce site. You need to attach your tracking data to the browser's global window object (e.g., window.ecommerceTracker = ...).
TypeScript's core DOM library already defines what Window looks like. If Window was a type, you would be blocked. You couldn't edit it. But because Window is an interface, you can use Declaration Merging.
// Somewhere deep in TypeScript's lib.dom.d.ts (You don't own this)
interface Window {
location: Location;
document: Document;
}
// In YOUR analytics.ts file (You own this)
interface Window {
ecommerceTracker: {
trackClick: (elementId: string) => void;
};
}
// Result: TypeScript silently merges them together. No errors!
window.ecommerceTracker.trackClick('buy-button');
The Interview Takeaway: Interfaces auto-merge if they have the same name in the same scope. This makes them the only choice when you are writing a public library or extending existing browser APIs, because you want your users to be able to augment your blueprints.
Scenario 2: The "Buy Now" Button State (Why you NEED type)
Now you are building the interactive "Buy Now" React component. The button has a specific lifecycle. It is either 'idle', 'loading', 'success', or 'failed'.
If you try to write this with an interface, it is impossible. Interfaces must be shapes (objects). They cannot be standalone unions or primitive values.
// ERROR: Interfaces can't do this.
interface ButtonStatus = 'idle' | 'loading' | 'success' | 'failed';
// SUCCESS: Types are flexible formulas.
type ButtonStatus = 'idle' | 'loading' | 'success' | 'failed';
// Now let's use advanced Type math (Utility Types) for our Props
type BaseProps = {
status: ButtonStatus;
label: string;
};
// We want a new type that makes 'label' optional just for the Icon version of the button
type IconButtonProps = Omit<BaseProps, 'label'> & { icon: string };
The Interview Takeaway: Types are mandatory when you need to express a Union (|), an Intersection (&), a primitive (string), or when you are doing "Type Math" (Mapped Types, Conditional Types, or using utilities like Omit and Pick).
The 10-Second Architect Cheat Sheet
When the interviewer asks, "What is the practical difference?", deliver this exact structured response:
- Declaration Merging: Interfaces merge automatically if declared multiple times. Types throw a "duplicate identifier" error. This makes interfaces mandatory for library authors creating public, extensible APIs.
- Capabilities: Interfaces can only describe objects (and classes). Types can describe objects, but also unions, intersections, primitives, and tuples.
-
Advanced Type System: Types unlock TypeScript's advanced functional programming features. You cannot do Mapped Types or Conditional Types (like checking
if T extends string) inside an interface definition. - Performance Context: For massive, highly nested structures (like a GraphQL schema response), the TypeScript compiler caches interfaces slightly faster than deeply intersected types, though in 99% of apps, this is unnoticeable.
Top comments (0)