Introduction

TypeScript gives us a handful of tools to ensure the best developer experience and application-level sustainability; two of these tools are Type Aliases and Interfaces.

We’ve previously written articles on both of these ways of defining new named types; if you haven’t already checked those articles out, be sure to use the links below:

Afreadingread the articles, you might ask yourself: “What even is the difference between the two? Why would I use one instead of the other?”.

What’s the difference between Type Aliases and Interfaces?

To have a better overview of the differences between the two, I believe it’d be best to go over the main features that both ways of defining types have and what features does one have that the other doesn’t.

Storing Primitive Types

This is something that we cannot do with Interfaces but can with Type Aliases.

While we can define types using Interfaces, we can only do under an Object type, while Type Aliases allow us to store an actual primitive as a type:

interface MyString {
  myString: string;
}

const str: MyString = 'hey'; // Compilation Error
type MyString = string;

const str: MyString = 'hey'; // Works fine

Ability To Extend and Intersect

While extending and intersecting types is not the same process, we’ll just put them together for the sake of this example.

The differences between the two arise when the type keys appear in both types that you want to extend or intersect from:

  • The Type Alias will allow you to do it but will change that type to never
  • The interface will throw an error mentioning that the types are not compatible
interface Response {
  success: boolean;
  error?: {
    message: string;
  };
}

interface Data extends Response {
  data: {
    myDataEntry: Record<string, any>;
  };
}
type Response = {
  success: boolean;
  error?: {
    message: string;
  };
};

type Data = Response & {
  data: {
    myDataEntry: Record<string, any>;
  };
};

Mapped Object Types

Type the keys in your objects with this.

Here are a few practical applications of this:

  • [key: string]: only strings are allowed as keys
  • [key: number]: only numbers are allowed as keys
  • [key in keyof T as `get${Capitalize<string & key>}`]: only allow keys that start with get…, e.g., as seen in a Getter object
interface Count {
  [key in 'not-a-number' | 'not-a-number-neither']: number; // Will throw compilation error
};
type Count = {
  [key in 'not-a-number' | 'not-a-number-neither'] // Works fine
};

Unions

Typescript’s equivalent to OR; the type can be any of a, b, c, as well as any others.

interface NumberOrString = string | number; // Compilation Error
type NumberOrString = string | number; // Works fine

Augmenting Existing Types

While Type Aliases cannot be augmented, Interfaces can; that’s also known as Interface Declaration Merging:

interface Car {
  model: string;
  engineSize: number;
}

interface Car {
  manufacturer: string;
}

interface Car {
  color: string;
}

const car: Car = {
  color: 'red',
  engineSize: 1968,
  manufacturer: 'BMW',
  model: 'M4',
  numSeats: 4, // `numSeats` property is not specified
};
type Singer = { // Duplicate identifier "Singer" ts(2300)
  songs: Song[];
};

type Singer = { // Duplicate identifier "Singer" ts(2300)
  name: string;
};

Tuples

If you’ve used hooks in react, then you know the usefulness of tuples.

A single function call can return an array of values and functions that are destructured and can be used as fully typed variables: const [name, setName] = useState('')

interface MyTuple extends Array<[string, number]> {};
type MyTuple = [string, number];

Functions

Functions can be annotated with Parameter Types and Return Types.

interface SetPointCoord {
  (x: number, y: number): void;
};
type SetPointCoord = (x: number, y: number) => void;

Recursion

Recursion is simple to use (At least when defining data types in TypeScript).

Make sure you add the optional ? to the recursive property. Otherwise, the TS compiler spits out an error upon searching an endless recursion.

interface MyNestedObject = {
  myProperty: string;
  myOtherProperty: number;
  myNestedObject: MyNestedObject;
};
type MyNestedObject = {
  myProperty: string;
  myOtherProperty: number;
  myNestedObject: MyNestedObject;
};
👋 Hey, I'm Vlad Mihet
I'm Vlad Mihet, a blogger & Full-Stack Engineer who loves teaching others and helping small businesses develop and improve their technical solutions & digital presence.

💬 Leave a comment

Your email address will not be published.

We will never share your email with anyone else.