TypeScript Interfaces vs Types: When to Use Each
A clear, beginner-friendly decision guide to TypeScript interfaces and type aliases in 2026, with side-by-side examples, real-world rules, and the exact cases where one beats the other.
Almost every TypeScript codebase reaches the same fork in the road within its first hundred lines: should this object shape be an interface or a type? Both compile, both work, both look almost identical in autocomplete. So why does the choice matter, and how do you pick?
This guide walks through what each one actually is, where they behave the same, where they differ, and the exact rules a beginner can follow in 2026 to make the right call without re-reading the TypeScript handbook every time. If you are brand new to the language, start with What is TypeScript? A beginner's guide to static typing first, then come back here.
The Two Tools in One Sentence Each
An interface declares the shape of an object. It can be extended by other interfaces and reopened in the same file or another file to add more fields.
A type alias gives a name to any type expression — an object shape, a union, an intersection, a tuple, a function signature, a mapped type, anything that the type system can describe.
The single most useful summary: every interface can be written as a type alias, but not every type alias can be written as an interface.
Where They Behave Identically
For a plain object shape, the two are interchangeable.
interface UserI { id: string; name: string }
type UserT = { id: string; name: string };
function greetA(u: UserI) { return `Hi ${u.name}`; }
function greetB(u: UserT) { return `Hi ${u.name}`; }Both versions accept the same values, both produce the same compile errors, both show identical autocomplete. There is no runtime difference — TypeScript erases both at build time.
Where Interfaces Win
Two practical superpowers belong only to interfaces.
Declaration merging. You can declare the same interface twice and TypeScript merges them. This is essential when extending types from third-party libraries (think adding fields to Express's Request or to the global Window).
interface Request { user?: { id: string } }
// In another file or after an import, the same interface
// can be reopened to add fields. Type aliases cannot do this.Cleaner extends syntax for class hierarchies. When you write a class that needs to implement a contract, interfaces read more naturally and TypeScript's error messages line up better. If you are doing classic OOP, prefer interfaces.
Where Types Win
Type aliases handle several shapes that interfaces simply cannot describe.
Unions. This is the biggest one. Discriminated unions are the modern way to model "this value is one of several shapes" — success vs error, loading vs loaded, click vs keypress.
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
type Event =
| { kind: "click"; x: number; y: number }
| { kind: "key"; code: string };You cannot write either of those as an interface. Unions are type-alias territory.
Mapped, conditional, and utility types. Anything built on top of keyof, in, or extends lives as a type alias. The standard library uses them everywhere: Partial<T>, Pick<T, K>, Omit<T, K>, ReturnType<F>, Awaited<P>. All type aliases.
Tuples and primitive aliases. type Point = [number, number] and type ID = string are aliases; interfaces cannot describe a tuple or rename a primitive.
The 2026 Decision Rule
Here is the pragmatic rule almost every modern codebase converges on:
| Situation | Use |
|---|---|
| Plain object shape that may be extended later | interface |
Class implements something (class C implements X) | interface |
| You need to add fields to a third-party type | interface (declaration merging) |
Anything that involves a union or | | type |
| Mapped, conditional, or utility-type expressions | type |
| Tuple, function type, or alias for a primitive | type |
| Internal one-off shape used in one place | either, pick one and be consistent |
The shortest version: default to type, reach for interface when you need merging or a clean extends/implements story. That is the rule the React, Next.js, and most large open-source TypeScript codebases follow today.
A Real Example
Modelling an API response is the classic place this shows up. The success and error shapes are different, so you reach for a discriminated union — type alias only.
type ApiResponse<T> =
| { status: "ok"; data: T }
| { status: "error"; code: number; message: string };
interface User { id: string; name: string; email: string }
async function getUser(id: string): Promise<ApiResponse<User>> {
const r = await fetch(`/api/users/${id}`);
if (!r.ok) return { status: "error", code: r.status, message: r.statusText };
return { status: "ok", data: (await r.json()) as User };
}ApiResponse<T> must be a type because it is a union. User is happy as either; we used interface because future code might extend it.
Common Mistakes Beginners Make
- Switching style halfway through a file. Pick one for object shapes in a given file or module and stick with it.
- Using an interface for what is really a union. The compiler will not let you, and the error is confusing.
- Reopening a type alias to "add a field". You cannot. Use
&to intersect, or switch to an interface. - Worrying about runtime cost. There is none. Both forms erase at build time.
- Treating
extends(interfaces) and&(types) as identical. They mostly are, but&does fewer error checks for conflicting members.
Quick Reference
interfacefor object shapes, especially when extended or mergedtypefor unions, intersections, tuples, primitives, mapped/conditional types- Both compile to nothing at runtime
- Default to
typein modern code; reach forinterfacewhen you need merging or class semantics - Utility types (
Partial,Pick,Omit,Record,ReturnType) are always type aliases
Rune AI
Key Insights
- Both describe shapes; both erase at runtime; both produce the same autocomplete.
- Only interfaces support declaration merging and the cleanest
class implementsstory. - Only type aliases describe unions, tuples, mapped types, and conditional types.
- Default to
type; reach forinterfacefor extendable object shapes and library augmentation. - Stay consistent within a file — switching styles mid-file makes the code harder to scan.
Frequently Asked Questions
Is one faster to compile than the other?
Can I convert an interface to a type later?
Why do most React libraries use `type`?
Can I use both in the same project?
What does the TypeScript team recommend?
Conclusion
Interfaces and type aliases overlap most of the time. The differences only show up in unions, declaration merging, and class hierarchies. Default to type, reach for interface when you specifically need its powers, and stay consistent inside any one module. That single rule resolves every "interface vs type" debate you will ever have.