How interfaces are used in typescript
Emilien Jegou, Wed June 18 2025
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:
Using interfaces allow the use of the this keyword which can help shorten methods chaining and improve clarity e.g.:
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 (or interface augmentation) is a feature that allow multiple interface in the same scope to "merge" implicitely, e.g.:
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:
Yet we may still find use-cases for declaration merging, for example when trying to extends external interfaces.
Declaration merging can be used to extends global types in your application:
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:
import '@total-typescript/ts-reset'
The same principles is often found in module files for extending external libraries, express is a good example for it:
// 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.
type aliases have an implicit index signature, but interface don't, it's a subtle difference that could lead to errors e.g.:
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.
Should I use
interfaceunless I needtypefeatures or usetypeunless I needinterfacefeatures?
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.