blog post

TypeScript: Interfaces vs Types - Understanding the Difference

This blog post makes a deep dive into TypeScript's object type system, exploring interfaces and types, difference between them with various examples to help you master these types.

What Are Interfaces and Types In TypeScript

Interfaces and types belong to the object types in TypeScript.

Object types in TypeScript groups a set of properties under a single type.

Object types can be defined either using type alias:

typescript
type User = { name: string; age: number; email: string; }; const user: User = { name: "Anton", age: 30, email: "[email protected]" };

Or interface keyword:

typescript
interface User { name: string; age: number; email: string; } const user: User = { name: "Anton", age: 30, email: "[email protected]" };

Interfaces in TypeScript

Interfaces are extendable and can inherit from other interfaces using the extends keyword:

typescript
interface Employee extends User { employeeId: number; salary: number; } const employee: Employee = { name: "Jack Sparrow", age: 40, email: "[email protected]", employeeId: 1, salary: 2000 };

Classes in TypeScript can inherit from interfaces and implement them using the extends keyword.

You can also extend interfaces by declaring the same interface multiple times:

typescript
interface User { name: string; age: number; email: string; } interface User { phone: string; } const user: User = { name: "Anton", age: 30, email: "[email protected]", phone: "1234567890" };

In TypeScript interfaces, you can use property modifiers to define optional and readonly properties.

Optional Properties

These properties are not mandatory when creating an object:

typescript
interface User { name: string; age: number; email?: string; // Optional property } const user: User = { name: "Anton", age: 30 };

Readonly Properties

These properties cannot be modified after the object is created:

typescript
interface User { name: string; readonly age: number; email: string; } const user: User = { name: "Anton", age: 30, email: "[email protected]" }; // Error: Cannot assign to 'age' because it is a read-only property. // user.age = 31;

Index Signatures

Index signatures allow you to define the type of keys and values that an object can have.

Sometimes you may not know all the names of the properties during compile time, but you do know the shape of the values. In such use cases you can use an index signature to define the properties:

typescript
interface Names { [index: number]: string; } const names: Names = ["Anton", "Jack"];

You can also define dictionaries by using index signatures:

typescript
interface NameDictionary { [key: string]: string; } const dictionary: NameDictionary = { name: "Anton", email: "[email protected]" };

There is a limitation when working with index signatures: you can't have named properties of another type:

typescript
interface NumberDictionary { [index: string]: number; length: number; // Error: Property 'name' of type 'string' is not assignable to 'string' index type 'number'. name: string; }

You can bypass this limitation my using Unions:

typescript
interface Dictionary { [index: string]: number | string; length: number; name: string; } const dictionary: Dictionary = { name: "Anton", email: "[email protected]", length: 5 };

It might be useful to make index signatures readonly to prevent assignment to their indexes:

typescript
interface ReadonlyDictionary { readonly [index: number]: string; } const dictionary: ReadonlyDictionary = { 0: "Anton" }; dictionary[0] = "John"; // Error: Index signature in type 'ReadonlyDictionary' is readonly.

Types in TypeScript

The type in TypeScript is similar to the interfaces but you can't extend existing types or add new properties to them.

typescript
type User = { name: string; age: number; email: string; }; type User = { phone: string; }; // Error: Duplicate identifier 'User'.

Combining Interfaces and Types with Intersection Types

Intersection types in TypeScript allow you to combine multiple types into one. An intersection type can be created by combining multiple interfaces or types using & operator:

typescript
interface Person { name: string; } interface ContactDetails { email: string; phone: string; } type Customer = Person & ContactDetails; const customer: Customer = { name: "Anton", email: "[email protected]", phone: "1234567890" };

In these examples a Customer type is an intersection type that combines all properties from Person and ContactDetails interfaces.

It's important to note that you can only declare an Intersection Type with a type keyword. You can't declare an interface type that holds an Intersection.

Intersections are not limited just to interfaces and types, they can be used with other types, including primitives, unions, and other intersections.

Combining Interfaces and Types with Union Types

Union type in TypeScript is a type that is formed from two or more other types. Union types are also called Discriminated Unions.

A variable of union type can have one of the types from the union.

Let's explore an example of geometrics shapes, that have common and different properties:

typescript
interface Circle { type: "circle"; radius: number; } interface Square { type: "square"; square: string; } type Shape = Circle | Square;

Let's create a function that calculates a square for each shape:

typescript
function getSquare(shape: Shape) { if (shape.type === "circle") { return Math.PI * shape.radius * shape.radius; } if (shape.type === "square") { return shape.square; } } const circle: Circle = { type: "circle", radius: 10 }; const square: Square = { type: "square", size: 5 }; console.log("Circle square: ", getSquare(circle)); console.log("Circle square: ", getSquare(square));

Here a getSquare function accepts a shape parameter that can be one of 2 types: Circle or Square. We need to define a property that will allow to distinguish types from each other. In our example it's a type property.

It's important to note that you can only create a Union Type with a type keyword. You can't declare an interface type that holds a Union Type.

Difference Between Interfaces and Types

Interfaces:

  • can inherit from other interfaces
  • can merge declarations, which is useful for extending existing objects
  • can't represent more complex structures, like unions and intersections

Types:

  • doesn't support inheritance
  • doesn't allow extending existing objects
  • can represent more complex structures, like unions and intersections

Summary

Both Interfaces and Types in TypeScript are quite the same but have some differences. Interfaces allow extension of existing objects, while types - not. On the other hand Types can represent unions and intersections.

Use Interfaces when you need to define the structure of an object and take advantage of declaration merging and extension. Use Types when you need you don't need them to be extended or to define complex types, such as unions or intersections.

There is no right choice whether to prefer Interfaces to Types or vice versa. You can choose based on the needs and personal preference, the most important part is to select a single approach that will be consistent in the project. My personal choice is Types as they can't be accidentally re-declared and extended, unless I need to do some fancy object-oriented stuff with interfaces and classes.

P.S.: you can find an amazing Cheat Sheets on Types and Interfaces from the Official TypeScript website.

Hope you find this blog post useful. Happy coding!

Improve Your .NET and Architecture Skills

Join my community of 100+ developers and architects.

Each week you will get 2 practical tips with best practises and architecture advice.