JavaScript & TypeScript DISCUSSION

TypeScript interface vs type alias: is there a real difference, and which should I pick?

Started by kimanzi TypeScript interfacetype aliasdeclaration mergingunion typesstructural typing
4 replies 248 views 5 participants
Latest activity · 30 Sep 2026

TypeScript interface vs type alias: is there a real difference, and which should I pick?

kimanzi JavaScript & TypeScript Forum
#1

I am typing the configuration and message objects for a small Node.js tool that talks to a PLC. Some examples declare shapes with interface Config { host: string; port: number } and others with type Config = { host: string; port: number }, and both seem to work identically when I assign objects and pass them to functions.

Is this purely a style choice, or are there things one form can do that the other cannot? I would like one consistent rule for the project rather than a mixture.

Community replies 4

Re: TypeScript interface vs type alias: is there a real difference, and which should I pick?

#2

For plain object shapes they are interchangeable. TypeScript's type system is structural, so an object is compatible when it has the required members, however the type was declared. A class can implements either, and an interface can extends an object type alias.

The differences are at the edges. type can name anything: unions such as type State = 'idle' | 'run' | 'fault', primitives, tuples such as type Point = [number, number], function types, and mapped or conditional types such as type ReadonlyCfg = Readonly<Config>. interface can only describe object shapes, which includes call signatures and index signatures.

Re: TypeScript interface vs type alias: is there a real difference, and which should I pick?

#3

What only interfaces can do is declaration merging. Two interface Config declarations in the same scope merge into one with the members of both. That is how a property is added to a library's or a global type, for example declare global { interface Window { plcBridge: Bridge } }. A second type Config is a duplicate identifier error, which is also an advantage: an alias cannot be reopened somewhere else by accident.

Extension differs too. interface B extends A { extra: number } reports an error at the declaration if extra conflicts with a member of A. The alias equivalent, the intersection type B = A & { extra: number }, does not complain; a conflicting property simply becomes never, and you find out when no value can be assigned to it.

Re: TypeScript interface vs type alias: is there a real difference, and which should I pick?

#4

Two practical differences show up in daily use. A value whose type is an object type alias can be passed where Record<string, unknown> is expected, while a value typed with an interface is rejected because it has no index signature; the reasoning is that an interface could be merged with further members later.

And in error messages and editor hovers an interface is shown by name, while an alias built from intersections and mapped types may be expanded into a long structure. The TypeScript performance guidance also notes that interface extends is cheaper for the compiler to check than large intersections, though that only matters in big codebases. Neither form exists at run time, so there is no instanceof Config; use a discriminant field or a type guard.

Re: TypeScript interface vs type alias: is there a real difference, and which should I pick?

#5

A rule that works: use interface for object shapes that are public API or that classes implement, such as interface Transport { send(data: Uint8Array): Promise<void> }, and type for everything else: unions, tuples, function signatures and compositions of utility types. The simpler alternative is type everywhere and interface only when merging is needed. Both are defensible; consistency matters more, and the typescript-eslint rule consistent-type-definitions enforces whichever you choose.

For PLC messages the core type is likely a union, type Message = ReadRequest | WriteRequest | ErrorReply. For register maps, const REG = { speed: 40001, torque: 40002 } as const; type RegName = keyof typeof REG; gives 'speed' | 'torque' without writing the names twice.

TEP COMMUNITY