Should I use interfaces or type aliases?

TLDR: It doesn't matter what you use, but types are a better option sometimes.
Developed by Microsoft, Typescript was released in October 2012 as a superset of JavaScript, adding static typing. However, in this initial version, types and interfaces were not yet available.
Interfaces were added in TypeScript 0.9 in June 2013, allowing developers to define entire object structures. Types, also known as type aliases, were introduced 1.5 years later. Today both play a crucial role in TypeScript by providing better code readability and type safety.
In most cases, we can use types and interfaces interchangeably. However, before discussing specific scenarios, let's examine how each is constructed.
Interface
interface Farm {
name: string;
numberOfAnimals: number;
hasFishPond: boolean;
}
Type
type Farm = {
name: string;
numberOfAnimals: number;
hasFishPond: boolean;
The only real difference in their construction is the keyword used and the fact that an equals sign is placed before the curly braces in a type declaration. Because both mechanisms were introduced at different times, their usage varied. Interfaces were recommended for certain situations, while types were suggested for others.
However, as TypeScript evolved, its functions and capabilities have become almost unified. Of course, the structure and code are different, but the result is similar or the same.
Extending types
When it comes to extending types, both interfaces and types have their methods that differ slightly.
Interfaces can extend other interfaces using the extends keyword.
interface Animal {
name: string;
}
interface Mammal extends Animal {
hasFur: boolean;
}
interface Pig extends Mammal {
makeSound(): void;
}
const myPig: Pig = {
name: 'Porky',
hasFur: false,
makeSound() {
console.log("Oink oink!")
}
}
The Pig interface extends the Mammal interface, which in turn extends the Animal interface. This means that any object that implements the Pig interface must have all the properties and methods defined in the Animal, Mammal, and Pig interfaces.
When using type aliases, you can achieve a similar effect using the intersection operator (&) instead of the extends keyword. The intersection operator allows you to combine multiple types into a single type.
type Animal = {
name: string;
};
type Mammal = Animal & {
hasFur: boolean;
};
type Pig = Mammal & {
makeSound(): void;
};
const myPig: Pig = {
name: "Porky",
hasFur: true,
makeSound() {
console.log("Oink oink!");
}
};
It is worth mentioning that types can also extend interfaces. In that example, we are combining the interface Animal with type Mammal in another type Dog
interface Animal {
name: string;
}
type Mammal = {
hasFur: boolean;
};
type Dog = Animal & Mammal & {
breed: string;
};
const myDog: Dog = {
name: "Buddy",
hasFur: true,
breed: "Labrador"
};
Interfaces only for objects
When you are using interfaces is not possible to use different structures than objects. On the other hand, aliases can be anything, boolean, string, number, doesn't matter.
type FarmName = string;
const myFarmName: FarmName = "Tom's Farm"
Such a simple definition of type is not possible in interfaces. We can try to define it similarly, but it will always be an object.
interface FarmName {
name: string;
}
const myFarmName: FarmName = {name: "Tom's Farm"}
Another thing that only types have are unions. We can define a couple of types. Perhaps in our example, we define FarmName as a string or array of string using | symbol. Now both myFirstFarmName and mySecondFarmName are valid.
type FarmName = string | string[];
const myFirstFarmName: FarmName = "Tom's Farm"
const mySecondFarmName: FarmName = ['Katowice', 'Warsaw', 'New York']
There is one more thing that type can do and interfaces cannot. It is a defining function type. Function types allow you to specify the types of parameters and the return value of a function.
type FarmAnimal = "pig" | "cow" | "chicken";
type FarmAnimalSound = (animal: FarmAnimal) => string;
In the example, we typed the function FarmAnimalSound which is taking as a parameter FarmAnimal and returns a string.
Types are more predictable
When you are using an interface it is allowed to use the same name for different interfaces. In the end, it will be combined. At first, it can look convenient and useful, but it can be also unpredictable. You have no control, if someone in the project names his interface the same and as a result, it will cause an error in your codebase.
interface Farm {
name: string;
}
interface Farm {
numberOfAnimals: number;
hasFishPond: boolean;
}
const myFarm: Farm = {
name: "Tom's Farm",
numberOfAnimals: 22,
hasFishPond: false
}
It is a saver to use types and have full control over what type you have.
type Farm = {
name: string;
}
type Farm2 = Farm & {
numberOfAnimals: number;
hasFishPond: boolean;
}
const myFarm: Farm2 = {
name: "Tom's Farm",
numberOfAnimals: 22,
hasFishPond: false
}
Class type implementation
In the early versions of TypeScript, implementing types in classes was only possible through interfaces.
interface Farm {
name: string;
numberOfAnimals: number;
}
class MyFarm implements Farm {
name: "Tom's Farm";
numberOfAnimals: 2;
}
However, it is possible now to use type aliases also. There is no difference.
type Farm = {
name: string;
numberOfAnimals: number;
}
class MyFarm implements Farm {
name: "Tom's Farm";
numberOfAnimals: 2;
}
Summary
First and foremost, types are more flexible than interfaces. They can represent not only objects but also simple types such as numbers, strings, or boolean values. Moreover, types allow for defining function types, which is not possible with interfaces.
Types also ensure greater predictability and control over the type structure in a project. Unlike interfaces, which are automatically merged if they have the same name, types require unique names, eliminating the risk of unexpected conflicts.
However, it's worth noting that interfaces may offer better readability when defining object structures. The interface keyword communicates the intent of describing the shape of an object, making the code more self-explanatory.
Ultimately, the choice between interfaces and types depends on the specific needs of the project and the preferences of the development team.