Published on April 23, 2025
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.
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.
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.
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.
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.
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:
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:
You can declare interface properties as readonly
so that they cannot be modified after they are initialized.
You can also define properties as optional using the ?
symbol, so that you can omit them without causing a type error:
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.
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).
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 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.
There are several best practices you can follow to best utilize TypeScript interfaces.
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:
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.
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.
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.