TypeScript interview question
What is the difference between an interface and a type alias in TypeScript?
Short answer
Both describe object shapes and are mostly interchangeable. Interfaces can be reopened through declaration merging and extended with extends; type aliases can name any type, including unions, tuples, primitives, mapped and conditional types. Many teams use interfaces for object contracts and types for everything else.
Answer
For describing the shape of an object, interface and type are nearly interchangeable, and you can implement either with a class. The differences are at the edges: type aliases can name any type (unions, tuples, primitives, function types, mapped and conditional types), while interfaces only describe object shapes but support declaration merging and give slightly clearer error messages and extends semantics.
Same result, two syntaxes
interface User {
id: number;
email: string;
}
type UserT = {
id: number;
email: string;
};
const a: User = { id: 1, email: 'a@example.com' };
const b: UserT = a; // structurally identical, so assignable
TypeScript is structurally typed, so the name does not matter; the shape does.
Things only type aliases can do
type Browser = 'chromium' | 'firefox' | 'webkit'; // union of literals
type Viewport = [width: number, height: number]; // tuple
type Id = string | number; // union of primitives
type Handler = (req: Request) => Promise<Response>; // function type (interfaces can, but less readable)
type ReadonlyDeep<T> = { readonly [K in keyof T]: ReadonlyDeep<T[K]> }; // mapped
type ElementOf<T> = T extends (infer U)[] ? U : never; // conditional
Unions are the big one. Modelling an API response as a discriminated union is a type-alias job:
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: { code: string; message: string } };
Things only interfaces can do: declaration merging
Declaring the same interface twice merges the members. This is how libraries let you augment their types:
// Augment a global type
declare global {
interface Window {
__APP_STATE__: { userId?: string };
}
}
The same mechanism is used to add custom properties to framework types. With type, a second declaration of the same name is an error.
Extending
interface Admin extends User { permissions: string[] }
type AdminT = UserT & { permissions: string[] };
extends reports a clear error when a property conflicts. An intersection with conflicting property types silently produces never for that property, which can be confusing. TypeScript’s own performance guidance also recommends interfaces over intersections, because the compiler can cache and check them faster in large codebases.
A practical convention
interfacefor object contracts that might be extended or implemented: page object interfaces, fixture types, public API shapes.typefor unions, tuples, function signatures and anything derived with utility or mapped types.- Enforce consistency with the
@typescript-eslint/consistent-type-definitionsrule so the debate happens once.
What interviewers look for
- Recognition that they mostly overlap for object shapes.
- Specific capabilities: unions and mapped types for
type, merging forinterface. - A concrete use of declaration merging (globals or module augmentation).
- A pragmatic convention rather than dogma.
A strong answer covers
- Both can describe object shapes and are often interchangeable
- Type aliases can represent unions, tuples, primitives, mapped and conditional types
- Interfaces support declaration merging
- Extension: extends for interfaces, intersections (&) for types
- States a sensible team convention