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.

TypeScriptbeginner
10 min read

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.

index.tsindex.ts
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).

index.tsindex.ts
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.

index.tsindex.ts
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:

SituationUse
Plain object shape that may be extended laterinterface
Class implements something (class C implements X)interface
You need to add fields to a third-party typeinterface (declaration merging)
Anything that involves a union or |type
Mapped, conditional, or utility-type expressionstype
Tuple, function type, or alias for a primitivetype
Internal one-off shape used in one placeeither, 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.

index.tsindex.ts
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

  • interface for object shapes, especially when extended or merged
  • type for unions, intersections, tuples, primitives, mapped/conditional types
  • Both compile to nothing at runtime
  • Default to type in modern code; reach for interface when you need merging or class semantics
  • Utility types (Partial, Pick, Omit, Record, ReturnType) are always type aliases
Rune AI

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 implements story.
  • Only type aliases describe unions, tuples, mapped types, and conditional types.
  • Default to type; reach for interface for extendable object shapes and library augmentation.
  • Stay consistent within a file — switching styles mid-file makes the code harder to scan.
RunePowered by Rune AI

Frequently Asked Questions

Is one faster to compile than the other?

For small files, no. For very large unions, type aliases can slow the compiler. For very deep interface chains, interfaces can. Almost no real codebase has noticeable problems.

Can I convert an interface to a type later?

Usually yes — `interface User { id: string }` becomes `type User = { id: string }`. The reverse is only possible if the type alias does not use unions, mapped types, or tuples.

Why do most React libraries use `type`?

Because props often involve unions (`type ButtonProps = ... | ...`), and being consistent across all exported types is easier than mixing styles.

Can I use both in the same project?

Yes. Most real codebases do. Use the right tool for each shape.

What does the TypeScript team recommend?

The handbook says either is fine. The community has converged on "default to type, use interface when you need merging or class implements". Follow that and you will fit in everywhere.

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.