How interfaces are used in typescript

Emilien Jegou, Wed June 18 2025

In typescript, we often use interface and type intercheangably, we tend to favorise type as they represent a higher subset of features, e.g. you can represent inheritacne with both interface and types but only with the second can you perform type mapping e.g. Partial or Omit.

It seems only logical that if you have two almost identical solution you would choose the more feature-full option, especially when it’s syntax is also generally shorter. But in truth there are things that only interfaces can do:

"This" keyword

Using interfaces allow the use of the this keyword which can help shorten methods chaining and improve clarity e.g.:

typescript
interface MyChain<A, B, C, D> {
   chainA(a: A): this;
   chainB(b: B): this;
   chainC(b: C): this;
   chainD(b: D): this;
   execute(): My;
}

// You cannot use the "this" keywords in type.
type MyChain<A, B, C, D> = {
   chainA(a: A): MyChain<A, B, C, D>;
   chainB(b: B): MyChain<A, B, C, D>;
   chainC(b: C): MyChain<A, B, C, D>;
   chainD(b: D): MyChain<A, B, C, D>;
   execute(): My;
}

Declaration merging

Declaration merging (or interface augmentation) is a feature that allow multiple interface in the same scope to "merge" implicitely, e.g.:

typescript
interface MyInterface {
    age: number;
}

interface MyInterface {
    name: string;
}

const x: MyInterface = { age: 123, name: 'John Doe' }

This is a feature that is generally avoided by developers for a few reason:

  • it doesn’t work well with LSP or typescript (e.g. goto definition)
  • it makes your code harder to navigate
  • Cognitively it's confusing and bug-inducing

Yet we may still find use-cases for declaration merging, for example when trying to extends external interfaces.

On Typescript default

Declaration merging can be used to extends global types in your application:

typescript
interface Window {
  myAppVersion: string;
}

window.myAppVersion = "1.2.3";

This is the how the library ts-reset improve javascript standard library type-safety, a single import can avoid many typescript pitfall; this is only possible through declaration merging:

typescript
import '@total-typescript/ts-reset'

On external libraries

The same principles is often found in module files for extending external libraries, express is a good example for it:

typescript
// global.d.ts or in a module augmentation block
declare module 'express' {
  interface Request {
    user?: { id: string; role: string };
  }
}

If you declare your external interfaces as type's in your application 3rd party user will not be able to extend them in the same manner.

If your package has weak typing (e.g. overly resort to “any” or “unknown” on external interface). Your users could then resort to declaration merging for covering your type declaration as a palliative solution.

Implicit index signatures

type aliases have an implicit index signature, but interface don't, it's a subtle difference that could lead to errors e.g.:

typescript
type DemoType = { hello: number };

const t : { [k: number]: string } = { hello: 123 } as DemoType;

// vs

interface DemoInterface { hello: number }

// Type 'DemoInterface' is not assignable to type '{ [k: number]: string; }'.
const i : { [k: number]: string } = { hello: 123 } as DemoInterface;

The implicit type signature on type actually create an invalid casting: DemoType doesn't have any numeric keys or string values. this is sort of a quirky scenario but worth mentionning, I actually prefer how interfaces are stricter by default on this particular case.

Worth noting that class will behave in the same way as interfaces even when they are extending a type.

So which one should I use?

Should I use interface unless I need type features or use type unless I need interface features?

In general you will need type-only features more often than you will need the interface-only one, so I would recommend making it the default choice.

You can still use interface for inheritance and having access to the this keyword when it simplify your types. You will rarely encounter cases where declaration merging can be usefull, only when encountering third-party cases such as the one presented above, so don't focus on it too much.

Other than that, you may feel more comfortable using interface when coding in OOP, since it's meaning is deeply ingrained, and that's perfectly fine too.