Mastering TypeScript: A Guide to Choosing Between ‘type’ and ‘interface’

In the TypeScript realm, 'type' and 'interface' play pivotal roles. This article breaks down their distinctions and offers insights on when to leverage each, ensuring more robust TypeScript code.

TypeScript has become a reliable tool for catching errors and improving the stability of code. However, when working with TypeScript, I’ve often wondered about the differences between the type and interface keywords and when to use each one.

In this blog post, I want to share what I’ve learned about the differences between type and interface and when to use each one. I'll provide clear examples and explanations to help you better understand how to use these two powerful tools in your TypeScript projects.

By the end of this post, you’ll have a better understanding of the differences between type and interface and how to use them effectively in your TypeScript code. I hope this post will be helpful to other web developers who are looking to improve their TypeScript skills and write more reliable code.

Type vs Interface

In TypeScript, both type and interface can be used to define object types, but they have some differences in syntax and functionality. interface is a keyword used to define object types and has the syntax.

interface IUser {  
	name: string;  
    age: number;  
    ...
 }

On the other hand, type is a keyword used to define object types and has the syntax:

type User = {
  name: string;
  age: number;
  ...
};

The syntax difference is minor, but there are other differences in functionality.

interface can be extended to create new types that inherit the properties of the parent interface:

type Union = Type1 | Type2;

type Intersection = Type1 & Type2;

These types allow you to define more complex relationships between types and can help you model data more accurately.

Example — Building a Messaging App

Let’s say you’re building a messaging app, and you want to define a type for a message that can contain either text or an image. You can use a union type to define this:

type Message = {
  type: 'text' | 'image';
  content: string | File;
}

In this example, we use type to define a union type that can only be one of two string values: 'text' or 'image'. We also use a union type to define the content property, which can be either a string (for text messages) or a File object (for image messages).

Now let’s say you want to define a type for a group of users who have different roles in your app, such as admin, moderator, and regular user. You can use an intersection type to define this:

type User = {
  id: number;
  name: string;
}

type Admin = User & {
  role: 'admin';
  permissions: string[];
}

type Moderator = User & {
  role: 'moderator';
  canDelete: boolean;
}

type RegularUser = User & {
  role: 'user';
  isPremium: boolean;
}

In this example, we define a User type with two properties: id and name. We then define three other types (Admin, Moderator, and RegularUser) that extend the User type using an intersection type (&). Each of these types has a different role property and additional properties specific to their role.

Using union and intersection types with type can help you define more complex types in your TypeScript code and make it easier to model real-world data.

Declaration Merging

interface also supports declaration merging, which allows you to define multiple interfaces with the same name and merge their properties into a single interface:

interface MyObject {
  property1: Type1;
}

interface MyObject {
  property2: Type2;
}

const myObject: MyObject = {
  property1: 'value1',
  property2: 'value2'
};

This can be useful when you’re working with third-party libraries or systems that define their interfaces separately and need to merge them into a single interface.

Example — Unleashing the Power of Declaration Merging

Let’s say you’re building an app that requires a user to sign in and you want to define an interface for the user object returned by your authentication API. You might define the interface like this:

interface User {
  username: string;
  email: string;
}

Later, you find out that the authentication API also returns a userId property for each user, but you don't want to modify the original User interface. Instead, you can define a new interface with the same name and it will automatically merge the properties with the original User interface:

interface User {
  userId: number;
}

// Now the User interface has three properties: username, email, and userId
const user: User = {
  username: 'john.doe',
  email: 'john.doe@example.com',
  userId: 123
};

In this example, we define the User interface with two properties: username and email. We then define a new User interface with the userId property. When we use the User interface to define our user object, the properties from both interfaces are merged together to create an object with three properties: username, email, and userId.

Declaration merging is a useful feature of interface that allows you to extend existing interfaces without modifying their original definition.

Let’s talk about the Open/Closed Principle

Ah, the Open/Closed Principle (OCP). One of the five SOLID principles of object-oriented programming design. It’s like the holy grail of software development. Everyone talks about it, but do they really understand it? Or are they just pretending?

I hope you remember: SOLID is an acronym that stands for five principles of object-oriented programming design that were introduced by Robert C. Martin (also known as Uncle Bob). These principles are intended to make software development more scalable, maintainable, and extensible.

The previous example is a good illustration of the Open/Closed Principle because it shows how you can extend an interface without modifying its original definition. By defining a new interface with the userId property, rather than modifying the original User interface, you're keeping the original interface closed for modification while still extending its functionality. This approach makes it easier to maintain and evolve your code over time without introducing unexpected issues or breaking changes.

But wait, there’s more! If you use TypeScript or another language with declaration merging, you can have your cake and eat it too. You can define the User interface with just two properties, and then define a new User interface with the userId property. When you use the User interface to define your user object, the properties from both interfaces magically merge together to create an object with three properties. It's like magic, but with less rabbits and more TypeScript.

But let’s be real, who has time for all this OCP nonsense? Sometimes you just need to get stuff done, and modifying existing code is the fastest way to do it. Who cares about the OCP anyway? It’s just a fancy principle that software developers like to throw around to make themselves sound smart.

So, go ahead and violate the OCP if you must. Just make sure you have a good reason for doing so. And if anyone asks, just tell them you’re following the KCP (Keep it Complacent Principle). That’s a principle we can all get behind.

React Component Props

So you’re wondering whether to use types or interfaces to define your React component props? Well, let me tell you, you’ve come to the right place. I mean, who needs a life when you can spend your days debating the finer points of TypeScript syntax? You’re like a modern-day Socrates, asking the important questions like “should I use type or interface?" while the rest of us plebs are busy swiping left on Tinder. So buckle up, my friend, because we're about to dive deep into the exciting world of type definitions for React components.

Using type or interface to define props in a React component is largely a matter of personal preference, as both options provide similar functionality. However, there are a few differences to consider when choosing between them.

interface is a commonly used approach for defining props in React components. This is because interface allows you to define the shape of the props object in a way that makes it easy to extend and reuse. For example, you can create a base Props interface that defines the common props for a group of components and then extend it for each individual component as needed:

interface Props {
  className?: string;
  onClick?: () => void;
}

interface ButtonProps extends Props {
  type?: 'button' | 'submit' | 'reset';
}

function Button({ className, onClick, type }: ButtonProps) {
  // render button with props
}

In this example, we define a Props interface with two optional props: className and onClick. We then extend the Props interface to create a ButtonProps interface that adds an optional type prop. We can then use the ButtonProps interface to define the props for our Button component.

type, on the other hand, is useful when you need to define more complex types, such as union or intersection types, in your props. For example, you can use type to define a Size type that represents different size options for a component:

type Size = 'small' | 'medium' | 'large';

interface Props {
  size: Size;
}

function Component({ size }: Props) {
  // render component with props
}

In this example, we use type to define a Size type that represents the different size options for our component. We then use the Props interface to define the size prop, which is required and can only be one of the values in the Size type.

In general, using interface is a good choice for defining simple props that can be extended or reused, while using type is a good choice for defining more complex types in your props.

In conclusion, both type and interface can be used to define props in a React component, but they have some differences in functionality. interface is useful for defining simple props that can be extended or reused, while type is useful for defining more complex types in your props. Ultimately, the choice between type and interface is a matter of personal preference and the specific needs of your project.

Summary

In general, interface is often used to define the shape of objects that are used as interfaces with other systems or libraries. type, on the other hand, is often used to define more complex types, such as unions, intersections, or mapped types, or to define types that cannot be expressed as interfaces, such as function types or conditional types.

Cheers!

Subscribe to Rico Fritzsche

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe