Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interfaces vs Types in TypeScript

People also ask

What is difference between interface and type in TypeScript?

The typescript type supports only the data types and not the use of an object. The typescript interface supports the use of the object. Type keyword when used for declaring two different types where the variable names declared are the same then the typescript compiler will throw an error.

When should you use type class or interface in TypeScript?

When should we use classes and interfaces? If you want to create and pass a type-checked class object, you should use TypeScript classes. If you need to work without creating an object, an interface is best for you.

What is type VS interface?

// One major difference between type aliases vs interfaces are that interfaces are open and type aliases are closed. This means you can extend an interface by declaring it a second time. // In the other case a type cannot be changed outside of its declaration.

What is the use of interface in TypeScript?

An interface is a syntactical contract that an entity should conform to. In other words, an interface defines the syntax that any entity must adhere to. Interfaces define properties, methods, and events, which are the members of the interface.


2019 Update


The current answers and the official documentation are outdated. And for those new to TypeScript, the terminology used isn't clear without examples. Below is a list of up-to-date differences.

1. Objects / Functions

Both can be used to describe the shape of an object or a function signature. But the syntax differs.

Interface

interface Point {
  x: number;
  y: number;
}

interface SetPoint {
  (x: number, y: number): void;
}

Type alias

type Point = {
  x: number;
  y: number;
};

type SetPoint = (x: number, y: number) => void;

2. Other Types

Unlike an interface, the type alias can also be used for other types such as primitives, unions, and tuples.

// primitive
type Name = string;

// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

3. Extend

Both can be extended, but again, the syntax differs. Additionally, note that an interface and type alias are not mutually exclusive. An interface can extend a type alias, and vice versa.

Interface extends interface

interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }

Type alias extends type alias

type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

Interface extends type alias

type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }

Type alias extends interface

interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };

4. Implements

A class can implement an interface or type alias, both in the same exact way. Note however that a class and interface are considered static blueprints. Therefore, they can not implement / extend a type alias that names a union type.

interface Point {
  x: number;
  y: number;
}

class SomePoint implements Point {
  x = 1;
  y = 2;
}

type Point2 = {
  x: number;
  y: number;
};

class SomePoint2 implements Point2 {
  x = 1;
  y = 2;
}

type PartialPoint = { x: number; } | { y: number; };

// FIXME: can not implement a union type
class SomePartialPoint implements PartialPoint {
  x = 1;
  y = 2;
}

5. Declaration merging

Unlike a type alias, an interface can be defined multiple times, and will be treated as a single interface (with members of all declarations being merged).

// These two declarations become:
// interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1, y: 2 };

Update March 2021: The newer TypeScript Handbook has a section Interfaces vs. Type Aliases which explains the differences.


Original Answer (2016)

As per the (now archived) TypeScript Language Specification:

Unlike an interface declaration, which always introduces a named object type, a type alias declaration can introduce a name for any kind of type, including primitive, union, and intersection types.

The specification goes on to mention:

Interface types have many similarities to type aliases for object type literals, but since interface types offer more capabilities they are generally preferred to type aliases. For example, the interface type

interface Point {
    x: number;
    y: number;
}

could be written as the type alias

type Point = {
    x: number;
    y: number;
};

However, doing so means the following capabilities are lost:

  • An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot No longer true since TS 2.7.
  • An interface can have multiple merged declarations, but a type alias for an object type literal cannot.

As of TypeScript 3.2 (Nov 2018), the following is true:

Aspect Type Interface
Can describe functions
Can describe constructors
Can describe tuples
Interfaces can extend it ⚠️
Classes can extend it 🚫
Classes can implement it (implements) ⚠️
Can intersect another one of its kind ⚠️
Can create a union with another one of its kind 🚫
Can be used to create mapped types 🚫
Can be mapped over with mapped types
Expands in error messages and logs 🚫
Can be augmented 🚫
Can be recursive ⚠️

⚠️ In some cases


Relevant in 2021

For typescrpt version: 4.3.4


TLDR;

My personal convention, which I describe below, is this:

Always prefer interface over type.

When to use type:

  • Use type when defining an alias for primitive types (string, boolean, number, bigint, symbol, etc)
  • Use type when defining tuple types
  • Use type when defining function types
  • Use type when defining a union
  • Use type when trying to overload functions in object types via composition
  • Use type when needing to take advantage of mapped types

When to use interface:

  • Use interface for all object types where using type is not required (see above)
  • Use interface when you want to take advatange of declaration merging.

Primitive types

The easiest difference to see between type and interface is that only type can be used to alias a primitive:

type Nullish = null | undefined;
type Fruit = 'apple' | 'pear' | 'orange';
type Num = number | bigint;

None of these examples are possible to achieve with interfaces.

💡 When providing a type alias for a primitive value, use the type keyword.

Tuple types

Tuples can only be typed via the type keyword:

type row = [colOne: number, colTwo: string];

💡 Use the type keyword when providing types for tuples.

Function types

Functions can be typed by both the type and interface keywords:

// via type
type Sum = (x: number, y: number) => number;

// via interface
interface Sum {
  (x: number, y: number): number;
}

Since the same effect can be achieved either way, the rule will be to use type in these scenarios since it's a little easier to read (and less verbose).

💡 Use type when defining function types.

Union types

Union types can only be achieved with the type keyword:

type Fruit = 'apple' | 'pear' | 'orange';
type Vegetable = 'broccoli' | 'carrot' | 'lettuce';

// 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce';
type HealthyFoods = Fruit | Vegetable;

💡 When defining union types, use the type keyword

Object types

An object in javascript is a key/value map, and an "object type" is typescript's way of typing those key/value maps. Both interface and type can be used when providing types for an object as the original question makes clear. So when do you use type vs interface for object types?

Intersection vs Inheritance

With types and composition, I can do something like this:

interface NumLogger { 
    log: (val: number) => void;
}
type StrAndNumLogger = NumLogger & { 
  log: (val: string) => void;
}

const logger: StrAndNumLogger = {
  log: (val: string | number) => console.log(val)
}

logger.log(1)
logger.log('hi')

Typescript is totally happy. What about if I tried to extend that with interface:


interface StrAndNumLogger extends NumLogger { 
    log: (val: string) => void; 
};

The declaration of StrAndNumLogger gives me an error:

Interface 'StrAndNumLogger' incorrectly extends interface 'NumLogger'

With interfaces, the subtypes have to exactly match the types declared in the super type, otherwise TS will throw an error like the one above.

💡 When trying to overload functions in object types, you'll be better off using the type keyword.

Declaration Merging

The key aspect to interfaces in typescript that distinguish them from types is that they can be extended with new functionality after they've already been declared. A common use case for this feature occurs when you want to extend the types that are exported from a node module. For example, @types/jest exports types that can be used when working with the jest library. However, jest also allows for extending the main jest type with new functions. For example, I can add a custom test like this:

jest.timedTest = async (testName, wrappedTest, timeout) =>
  test(
    testName,
    async () => {
      const start = Date.now();
      await wrappedTest(mockTrack);
      const end = Date.now();

      console.log(`elapsed time in ms: ${end - start}`);
    },
    timeout
  );

And then I can use it like this:

test.timedTest('this is my custom test', () => {
  expect(true).toBe(true);
});

And now the time elapsed for that test will be printed to the console once the test is complete. Great! There's only one problem - typescript has no clue that i've added a timedTest function, so it'll throw an error in the editor (the code will run fine, but TS will be angry).

To resolve this, I need to tell TS that there's a new type on top of the existing types that are already available from jest. To do that, I can do this:

declare namespace jest {
  interface It {
    timedTest: (name: string, fn: (mockTrack: Mock) => any, timeout?: number) => void;
  }
}

Because of how interfaces work, this type declaration will be merged with the type declarations exported from @types/jest. So I didn't just re-declare jest.It; I extended jest.It with a new function so that TS is now aware of my custom test function.

This type of thing is not possible with the type keyword. If @types/jest had declared their types with the type keyword, I wouldn't have been able to extend those types with my own custom types, and therefore there would have been no good way to make TS happy about my new function. This process that is unique to the interface keyword is called declaration merging.

Declaration merging is also possible to do locally like this:

interface Person {
  name: string;
}

interface Person {
  age: number;
}

// no error
const person: Person = {
  name: 'Mark',
  age: 25
};

If I did the exact same thing above with the type keyword, I would have gotten an error since types cannot be re-declared/merged. In the real world, javascript objects are much like this interface example; they can be dynamically updated with new fields at runtime.

💡 Because interface declarations can be merged, interfaces more accurately represent the dynamic nature of javascript objects than types do, and they should be preferred for that reason.

Mapped object types

With the type keyword, I can take advantage of mapped types like this:

type Fruit = 'apple' | 'orange' | 'banana';

type FruitCount = {
  [key in Fruit]: number;
}

const fruits: FruitCount = {
  apple: 2,
  orange: 3,
  banana: 4
};

This cannot be done with interfaces:

type Fruit = 'apple' | 'orange' | 'banana';

// ERROR: 
interface FruitCount {
  [key in Fruit]: number;
}

💡 When needing to take advantage of mapped types, use the type keyword

Performance

Much of the time, a simple type alias to an object type acts very similarly to an interface.

interface Foo { prop: string }

type Bar = { prop: string };

However, and as soon as you need to compose two or more types, you have the option of extending those types with an interface, or intersecting them in a type alias, and that's when the differences start to matter.

Interfaces create a single flat object type that detects property conflicts, which are usually important to resolve! Intersections on the other hand just recursively merge properties, and in some cases produce never. Interfaces also display consistently better, whereas type aliases to intersections can't be displayed in part of other intersections. Type relationships between interfaces are also cached, as opposed to intersection types as a whole. A final noteworthy difference is that when checking against a target intersection type, every constituent is checked before checking against the "effective"/"flattened" type.

For this reason, extending types with interfaces/extends is suggested over creating intersection types.

More on typescript wiki.



When to use type?


Generic Transformations

Use the type when you are transforming multiple types into a single generic type.

Example:

type Nullable<T> = T | null | undefined
type NonNull<T> = T extends (null | undefined) ? never : T

Type Aliasing

We can use the type for creating the aliases for long or complicated types that are hard to read as well as inconvenient to type again and again.

Example:

type Primitive = number | string | boolean | null | undefined

Creating an alias like this makes the code more concise and readable.


Type Capturing

Use the type to capture the type of an object when the type is unknown.

Example:

const orange = { color: "Orange", vitamin: "C"}
type Fruit = typeof orange
let apple: Fruit

Here, we get the unknown type of orange, call it a Fruit and then use the Fruit to create a new type-safe object apple.


When to use interface?


Polymorphism

An interface is a contract to implement a shape of the data. Use the interface to make it clear that it is intended to be implemented and used as a contract about how the object will be used.

Example:

interface Bird {
    size: number
    fly(): void
    sleep(): void
}

class Hummingbird implements Bird { ... }
class Bellbird implements Bird { ... }

Though you can use the type to achieve this, the Typescript is seen more as an object oriented language and the interface has a special place in object oriented languages. It's easier to read the code with interface when you are working in a team environment or contributing to the open source community. It's easy on the new programmers coming from the other object oriented languages too.

The official Typescript documentation also says:

... we recommend using an interface over a type alias when possible.

This also suggests that the type is more intended for creating type aliases than creating the types themselves.


Declaration Merging

You can use the declaration merging feature of the interface for adding new properties and methods to an already declared interface. This is useful for the ambient type declarations of third party libraries. When some declarations are missing for a third party library, you can declare the interface again with the same name and add new properties and methods.

Example:

We can extend the above Bird interface to include new declarations.

interface Bird {
    color: string
    eat(): void
}

That's it! It's easier to remember when to use what than getting lost in subtle differences between the two.