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.
Originally published at https://antondevtips.com.
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:
type User = {
name: string;
age: number;
email: string;
};
const user: User = {
name: "Anton",
age: 30,
email: "info@antondevtips.com"
};
Or interface
keyword:
interface User {
name: string;
age: number;
email: string;
}
const user: User = {
name: "Anton",
age: 30,
email: "info@antondevtips.com"
};
Interfaces in TypeScript
Interfaces are extendable and can inherit from other interfaces using the extends
keyword:
interface Employee extends User {
employeeId: number;
salary: number;
}
const employee: Employee = {
name: "Jack Sparrow",
age: 40,
email: "captain.jack@gmail.com",
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:
interface User {
name: string;
age: number;
email: string;
}
interface User {
phone: string;
}
const user: User = {
name: "Anton",
age: 30,
email: "info@antondevtips.com",
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:
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:
interface User {
name: string;
readonly age: number;
email: string;
}
const user: User = {
name: "Anton",
age: 30,
email: "john.doe@example.com"
};
// 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:
interface Names {
[index: number]: string;
}
const names: Names = ["Anton", "Jack"];
You can also define dictionaries by using index signatures:
interface NameDictionary {
[key: string]: string;
}
const dictionary: NameDictionary = {
name: "Anton",
email: "info@antondevtips.com"
};
There is a limitation when working with index signatures: you can't have named properties of another type:
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:
interface Dictionary {
[index: string]: number | string;
length: number;
name: string;
}
const dictionary: Dictionary = {
name: "Anton",
email: "info@antondevtips.com",
length: 5
};
It might be useful to make index signatures readonly to prevent assignment to their indexes:
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.
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:
interface Person {
name: string;
}
interface ContactDetails {
email: string;
phone: string;
}
type Customer = Person & ContactDetails;
const customer: Customer = {
name: "Anton",
email: "info@antondevtips.com",
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:
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:
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!
Originally published at https://antondevtips.com.
After reading the post consider the following:
- Subscribe to receive newsletters with the latest blog posts
- Download the source code for this post from my github (available for my sponsors on BuyMeACoffee and Patreon)
If you like my content — consider supporting me
Unlock exclusive access to the source code from the blog posts by joining my Patreon and Buy Me A Coffee communities!
Top comments (0)