TypeScript provides two ways to create custom types for our data and they include Type aliases and Interfaces. This article will discuss the similarities and differences between them, and the best use cases for each. Let's dive in!
Type aliases
A type alias is basically a name for any type. Type aliases can be used to represent not only primitives but also object types, union types, tuples and intersections. Let's take a look at some examples:
type userName = string;
type userId = string | number; // Union type
type arr = number[];
// Object type
type Person = {
id: userId; // We're making use of another type alias here
name: userName;
age: number;
gender: string;
isWebDev: boolean;
};
const user: Person = {
id: 12345,
name: "Tolu",
age: 1,
gender: "female",
isWebDev: true,
};
const numbers: arr = [1, 8, 9];
Type aliases are declared with the type
keyword preceding them. Think of them like regular JavaScript variables that are used to represent certain values. Wherever you use a variable name, it is evaluated to the value that it represents. Type aliases work in a similar manner. Wherever you annotate a type by its alias, the alias will evaluate to the type(s) that it stands for. Like variables, you can't declare the same type alias more than once. Aliases are also never inferred by TypeScript, you have to explicitly annotate them.
Interfaces
Interfaces are another way to name data structures e.g objects. The declaration syntax for interfaces is different from that of type aliases. Let's rewrite the type alias Person
above to an interface declaration:
interface Person {
id: userId;
name: userName;
age: number;
gender: string;
isWebDev: boolean;
}
Unions and Intersections
A union type is formed from two or more other types, and represents a value that can be any one of them. Intersections allow us to combine multiple existing types into a single type that has all the features of those types. Take the following example:
type Human = {
name: string;
speaks: boolean;
};
interface Dog {
name: string;
barks: boolean;
}
type HumanAndDog = Human & Dog; // Intersection
type HumanOrDogOrBoth = Human | Dog; // Union
let humanAndDog: HumanAndDog = {
// must have all the properties of Human and Dog
name: "Sparky",
speaks: false,
barks: true,
};
let humanOrDog: HumanOrDogOrBoth = {
// can have the properties of Human or Dog, or both
name: "Tolu",
speaks: true,
};
Type aliases and interfaces can be combined into one type
using unions or intersections, but cannot be combined into an interface
.
Tuples
Tuples are a way to type arrays with fixed lengths, accounting for every item in said array.
type Mix = [number, string, boolean];
const mix: Mix = [1, "banana", true];
In TypeScript, tuples can be declared with types but not interfaces. However, tuples can be used inside interfaces, like this:
interface Mix {
a: [number, string, boolean];
}
Declaration merging
If you declare one interface
with the same name more than once, TypeScript merges them into one declaration and will treat them as a single interface. This is called declaration merging.
interface Person {
name: string;
}
interface Person {
age: number;
}
type Num = numbers;
type Num = string; // Duplicate identifier Num
let user: Person = {
// has the properties of both instances of Person
name: "Tolu",
age: 0,
};
Declaration merging only works on interfaces. TypeScript will give an error if you declare the same type alias more than once.
Extends
Both type aliases and interfaces can be extended. However, the syntax differs. A derived type alias or interface has all the properties and methods of its base type alias or interface, and can also define additional members.
A type alias can extend another type alias using an ampersand:
type A = { x: number };
type B = A & { y: string };
Type aliases can also extend interfaces:
interface A {
x: number;
}
type B = A & { y: string };
Interfaces can extend type aliases with the extends
keyword:
type A = {
x: number;
};
interface B extends A {
y: string;
}
Interfaces can extend other interfaces the same way they extend type aliases. Interfaces can also extend multiple interfaces separated by commas.
interface A {
x: string;
y: number;
}
interface B {
z: boolean;
}
interface C extends A, B {
breed: string;
}
let c: C = {
x: "Sparky",
y: 4,
z: true,
};
Interfaces extending classes
TypeScript allows an interface to extend a class. When this happens, the interface inherits the members of the base class, including the private and public members, but not their implementations. This means that when you create an interface that extends a class with private members, only that class or its subclasses can implement that interface. This allows you to restrict the use of the interface to the class or subclasses of the class that it extends.
class Base {
greetFriend() {
console.log(`Hello!`);
}
}
// Interface extending the Base class
interface Derived extends Base {
giveGist(): void;
}
// New class that extends Base class and implements the Derived interface
class NewClass extends Base implements Derived {
giveGist() {
console.log("I saw this the other day...");
}
}
const c = new NewClass();
c.greetFriend(); // Hello!
c.giveGist(); // I saw this the other day...
Implements
TypeScript supports class-based object-oriented programming. As a result, it allows classes to implement both type aliases and interfaces using the implements
keyword. An error will be thrown if a class fails to correctly implement it.
// Interface being implemented by a class
interface A {
x: number;
y: number;
}
class SomeClass implements A {
x = 1;
y = 2;
}
// Type alias being implemented by a class
type B = {
x: number;
y: number;
};
class SomeClass2 implements B {
x = 1;
y = 2;
}
A class can also implement multiple interfaces separated by commas e.g class A implements B, C {}
. Note that a class can not implement or extend a type alias that represents a union type:
type C = { x: number } | { y: number };
// ERROR: A class can only implement an object type or intersection of object types with statically known members.
class SomeClass3 implements C {
x = 1;
y = 2;
}
It’s important to understand that an implements clause is only a check that the class can be treated as the interface type. It doesn’t change the type of the class or its methods at all. A common source of error is to assume that an implements clause will change the class type - it doesn’t!
— TypeScript official docs
Which should I use?
Type aliases and interfaces are very similar and you can choose freely between them. Personally, I use type aliases when defining primitive, union, intersection, function or tuple types. However, I make use of interfaces when defining object types or to take advantage of declaration merging.
Conclusion
We have seen the differences and similarities between type aliases and interfaces.
- Both type aliases and interfaces can be used to describe the shape of an object or a function signature. But the syntax differs.
- Declaration merging only works on interfaces and not type aliases.
- You cannot declare a union, intersection or tuple with the
interface
keyword. However, you can make use of them within an interface. - Classes can implement both type aliases and interfaces, but not type aliases that represent a union type.
I hope that you have gained something useful from this article. Kindly leave any questions or additional information in the comment section.
Thanks for reading!
Top comments (1)
Interfaces can in some cases be more performant and provide cleaner type checking messages:
github.com/microsoft/TypeScript/wi...