Brendan McKenzie

Typescript - interfaces and types

Wednesday, 21 April 2021

The type system in Typescript is absolutely incredible. Endlessly configurable. But there's one thing that has always confused me.

You can define object types using interfaces, you can also achieve the same result using type definitions.

1type MyType = { property: string; }
3// or
5interface MyType { property: string }

Typescript doesn't really care which one you use, the end result is basically the same.

Similarly, after trawling the internet for definitive guides on when to use which, there doesn't seem to be much of a consensus either.

That leaves us in a situation where it's left to personal opinion, which isn't great since, well, it's personal.

I come from a C# background, so my idea of an interface is a contract, defining what a class must provide to implement all the functionality defined by that contract.

In that regard - my rule for using interface or type in Typescript is simple:

Interfaces are for when you are doing OOP. You are going to have Typescript classes that provide functionality and you want them all to agree contractually to what that functionality is.

Types are basically DTOs (data transfer objects). They're used to define the shape of data that's being passed around the application.

Let's try a simple contrived example.

To demonstrate when an interface would be useful, let's build a cache.

1interface ICache {
2  get(key: string): Promise<any>;
3  set(key: string, value: string): Promise<void>;
6class InMemoryCache implements ICache {
7  private _cache = new Map<any>();
9  public get(key: string) { return _cache.get(key); }
11  public set(key: string, value: string) { _cache.set(key, value); }
14class RedisCache implements ICache {
15  private _client: RedisClient;
17  constructor(client: RedisClient) {
18    this._client = client;
19  }
21  public get(key: string) { return JSON.parse(await _client.get(key)); }
23  public set(key: string, value: any) { _cache.set(key, JSON.stringify(value)); }

Here we have 2 implementations of a cache, both that can be shared around and types as ICache without caring for their implementation.

Using this approach tends to lead to you having a lot more types and fewer interfaces. Which for lazy typists is a win as it saves you keystrokes.

At the end of the day - the choice is yours. This is simply how I approach the topic.