TypeScript interfaces: How-to guide, best practices, and code examples

Published on April 23, 2025

TypeScript Interfaces

TypeScript is a type-safe, modern programming language built on JavaScript that provides many features to help you write more reliable code. A major one of these features is interfaces that allow you to define the structure of the objects you use to store data in your applications. This article explains what TypeScript interfaces are and how you can use them to improve your codebase and build better apps, and shares some code examples and best practices to get you started.

What are TypeScript interfaces?

In TypeScript, an interface defines the "shape" of an object by letting you specify the properties and methods each object of a certain type can or must include. TypeScript uses interfaces to enforce the consistency of the structure of objects and for type checking, but they do not include any implementation information. This means that an interface only says which properties and methods make up a type of object, but does not include initialization values or actual function code.

Below is an example TypeScript interface to describe a Human object:

Notice that there is no data and no code; it describes only what properties and methods must be present, and their types. The interface can then be used as a "contract" that objects must satisfy for them to count as valid for the type that the interface describes.

Human object

TypeScript interfaces are similar to type aliases in that you can use them to define what an object looks like — but they serve different purposes. In addition to representing objects, type aliases can also represent primitives, unions, intersections, and tuples, but you cannot extend or merge type aliases like you can with interfaces.

Benefits of TypeScript interfaces

TypeScript builds on top of JavaScript to add modern programming language features that make developing reliable applications easier. Interfaces are a key part of this, enabling:

  • Type safety through type checking: Type errors are the bane of JavaScript developers. While dynamic typing is great while you're learning to code, it leaves the door open for accidentally using the wrong type of value (for example, assigning a string to a variable that is expected to be numeric, and then trying to do arithmetic with it). TypeScript interfaces protect against this when using custom objects.

  • Compile-time error handling: As TypeScript code compiles to JavaScript to run, the compilation process can catch type errors (and some other errors) during the compile stage, rather than at run time. This results in more reliable production applications.

  • Reusability: Interfaces make it easier to reuse code by providing a central definition for object types that you can then import across your codebase, ensuring consistency.

  • Extensibility and polymorphism: As interfaces are extensible and reusable, you can use polymorphism, a fundamental object-oriented programming (OOP) concept, to build new interfaces that extend existing ones, adding new properties and methods while sharing a common foundation. For example, a Train interface might extend a base Vehicle interface with the addition of a Carriages property.

  • Code readability and self-documentation: Giving interfaces, classes, properties, and methods descriptive names (rather than repeatedly defining objects) makes it easy to identify what objects are, and the purpose of the code that calls them. It also helps make your code more self-documenting: the intent of your code is more clear, so others (or future-you) can quickly understand what's going on when making later updates.

  • Autocompletion and linting: The self-documenting aspect of TypeScript also allows integrated development environments like Visual Studio Code to provide autocompletion and linting, scaffolding code for you by automatically creating properties for you to fill in, and highlighting type errors if your code does not pass values that conform to an interface definition.

TypeScript compiles to regular JavaScript code, giving you more powerful programming tools while still being able to run your applications across a wide array of compatible web browsers and environments.

Tutorial: how to use TypeScript interfaces

The below code snippet shows the creation of a TypeScript interface that you could then export in a module:

This code creates an Instrument interface with a numeric ID property, a string name, a string sound, and a function that makes it play(). Notice that the interface defines the return type of the function, but not the logic.

You can then import interfaces and use (and reuse) them as modules across your application:

You can then use the interface to enforce type safety in your TypeScript code:

The above example demonstrates TypeScript’s structural typing: An object counts as a match to an interface if it has all of the required properties and methods — whether or not you initially defined it as the same type. In this case, an object guitar matches the Instrument interface, and you can pass it to the playInstrument function, which only accepts Instrument type objects as a parameter. But if, for example, the id property were missing, TypeScript would throw an error.

How to use a TypeScript function interface

TypeScript function interfaces define a function's valid parameters and return types. They make sure that different functions all accept the same types of parameters and return the same type of data. The syntax is similar to defining a regular TypeScript interface:

The function interface example above specifies that a conforming function must accept a sound parameter (string value) and a count parameter (numeric value). It should not return a value (void). You would use this interface when defining functions by specifying it as their type:

How to use a TypeScript class interface

Classes can also implement interfaces, allowing you to define the structure of multiple different classes with shared attributes:

Here, an Instrument interface enforces the structure for a Saxophone class, which adds an additional color property. Additional classes can implement the same Instrument interface, for example, a Trombone class with a material property instead of a color property:

TypeScript interface readonly properties

You can declare interface properties as readonly so that they cannot be modified after they are initialized.

Optional object properties

You can also define properties as optional using the ? symbol, so that you can omit them without causing a type error:

Index signatures/indexable properties

Index signatures let you define the structure of an interface, even if you don't yet know what name each property is going to take, by letting you define dynamically named properties:

The above example shows a Film interface, where each film that implements it can have multiple reviews. Each review is associated with a userId that is used as the dynamic name for a property of the reviews object parameter, allowing you to assign and access reviews dynamically using a userId. If you try to access the review with an unknown userId, it will return a value of undefined, rather than causing a type error.

Using generics with TypeScript interfaces

Generics are reusable interfaces that can work with multiple data types:

In this example, <SoundType> acts as a placeholder for any type, making the Instrument interface generic (as it can accept any type of value). This means you can implement type safety, while leaving some room to maneuver when an interface or function interface may need to accept values of different types, or unexpected types (for example, when retrieving data from an external source).

Extending TypeScript interfaces

Code reusability is a core tenet of object-oriented programming. When working with multiple similar interfaces that share a common underlying structure, you can extend them:

Above, a base Animal interface defines what is required for an Animal object, while the Bear interface extends it to add species information that may not be necessary for other animals.

Polymorphism with TypeScript interfaces

Polymorphism is using objects of different types interchangeably. TypeScript interfaces facilitate this: they provide a way to make sure that different objects all have the properties and methods required for a certain task. This means the same functions can be used with different object types, and can also allow behavior to be determined at runtime by passing different objects without necessarily knowing what they will be in advance.

Above, both the Bear and Human classes implement the Animal interface, even though they behave differently. Each class has its own makeSound() function, and while they don't do the same thing, they satisfy the interface definition for a function that returns a string. This lets you use both classes interchangeably elsewhere in your code with type safety.

Best practices for using TypeScript interfaces

There are several best practices you can follow to best utilize TypeScript interfaces.

Use composition instead of inheritance

While inheritance using the extends keyword is useful for creating interfaces and classes that build on the properties and methods of others, it is not flexible for complex scenarios, because it promotes a rigid hierarchy. An alternative design concept is composition, in which you combine many reusable classes or interfaces rather than building monolithic classes. 

For example, rather than defining broad classes, you can create interfaces that define common behaviors, and implement them only on the classes that will use them:

Use composition instead of inheritance

Composition allows you to write more flexible code without deeply nested class hierarchies, making your code easier to understand and maintain. It also allows for greater flexibility by allowing different components to be interchanged seamlessly.

Keep TypeScript interfaces granular and focused

The primary benefit of inheritance and composition using TypeScript is code readability, reusability, and extensibility — benefits that are largely lost if you create broad interfaces and classes.

You should create interfaces that are focused on a small subset of shared functionality, and break up an interface into smaller parts if there are instances where you will only use one of the functions it covers. 

For example, rather than creating a single interface that defines all of the data and functions required for an object:

Split its concerns among interfaces focused on the tasks that you might need to perform:

This makes your code easier to debug and maintain by letting you update your code in one interface, knowing that it will not affect the behavior of others.

Leveraging TypeScript with GraphQL for end-to-end type safety

Type safety is also beneficial when loading data into your applications from your backend APIs, helping to ensure that data arriving from your backend databases conforms to the structure defined in your TypeScript interfaces. By combining TypeScript with GraphQL and introspection, you can map classes and interfaces from your back end to your front end for type safety across your entire codebase.

Contentful provides both REST and GraphQL API endpoints for retrieving content. You can model any kind of content (combining elements including text, images, and video) for all your apps, websites, newsletters, billboards, and other digital channels in one place. You can then tailor our content management system (CMS) interface for your use case, and use our SDKs (software development kits) for popular platforms and frameworks, so you can connect your apps, and realize the full value of your content investments.

Subscribe for updates

Build better digital experiences with Contentful updates direct to your inbox.

Meet the authors

David Fateh

David Fateh

Software Engineer

Contentful

David Fateh is a software engineer with a penchant for web development. He helped build the Contentful App Framework and now works with developers that want to take advantage of it.

Related articles

Tailwind CSS and React are popular development tools for building front ends. Here's how to use Tailwind CSS with React, and info about Tailwind/React Native.
Guides

Start a React app with Tailwind CSS in under 5 minutes

October 25, 2024

Front end as a service (FEaaS) speeds up development and reduces infrastructure costs. Here's what FEaaS is, how it works, and a list of FEaaS platforms.
Guides

Understanding front end as a service (FEaaS)

October 18, 2024

This comprehensive guide explores the key differences and possibilities of Rust and TypeScript and explains their type systems and error-handling capabilities.
Guides

Rust and TypeScript: A comprehensive guide to their differences and integration

December 4, 2024

Contentful Logo 2.5 Dark

Ready to start building?

Put everything you learned into action. Create and publish your content with Contentful — no credit card required.

Get started