A Simple Guide to Typescript Interfaces: Declaration & Use Cases
Use TypeScript interfaces to define object shapes, enforce compile-time structure, and build scalable, type-safe code.
TypeScript is a superset of JavaScript that introduces new features and helpful improvements to the language, including a powerful static typing system. By adding types to your code, you can spot or avoid errors early and get rid of errors at compilation.
In TypeScript, an interface is a way to describe the shape of an object. It specifies which properties an object is expected to have and the types of those properties. TypeScript uses type inference to infer object types from values in code, but interfaces must be explicitly declared by the developer.
In this guide, you will learn how to create and work with interfaces in TypeScript to help add improved functionality to your projects. We will introduce you to the concept in detail with use cases.
Declaring interfaces
An interface describes the shape of an object in TypeScript. They can be used to provide information about object property names and the datatypes their values can hold to the TypeScript compiler. An interface participates in TypeScript’s compile-time type checking for your functions, variables, or the class that is implementing the interface.
Interfaces ensure that values conform to the expected structure at compile time.
Interfaces can be explicitly described. They are not implicitly created by the compiler. We declare an interface using the interface keyword in a .ts file. The following example shows an example of the syntax:
interface Dimension {width: string;height: string;}
Here, we defined an interface with the properties width and height that are both strings. Now we can implement the interface:
interface Dimension {width: string;height: string;}let _imagedim: Dimension = {width: "200px",height: "300px"};
Let’s see another example. Here, we declare an interface named User with the interface keyword. The properties and methods that make up the interface are added within the curly brackets as key:value pairs, just like we add members to a plain JavaScript object.
interface User {id: number;firstName: string;lastName: string;getFullName(): string;}
We can use our newly created interface in code by adding an annotation to relevant objects. For example, here’s how to create a new user object with the User interface:
const user: User = {id: 12,firstName: "Josh",lastName: "",getFullName: () => `${firstName} ${lastName}`};
With this, our compiler knows what properties the user object is expected to have. If a property is missing, or if its value isn’t of the same type specified in the interface, the compiler will report a type-checking error.
Interface vs type: when each shines
You’ll often see Typescript interfaces and type aliases used to describe object shapes. They overlap a lot, but there are practical differences:
Interfaces support declaration merging. If two interface blocks share the same name, their members merge. This is handy for augmenting third-party types.
interface AppConfig { apiUrl: string; }// Later in another file:interface AppConfig { featureFlag?: boolean }// AppConfig now has both members.
Type aliases are more flexible with unions and computed types.
type APIResponse<T> = { data: T } | { error: string }
Prefer interfaces for public, extendable object contracts (SDKs, component props) and type for unions, primitives, and type-level composition. In many codebases, you’ll mix both.
Rule of thumb: Start with an interface for object shapes; reach for type when you need unions, intersections, gymnastics, or mapped/conditional types.
Class implementing interface
We can use an interface with a class using the keyword implements, for example:
class NameofClass implements InterfaceName {}
Let’s look at that interface working with a class. Here, we have an interface with width and height properties that are both type string. There is also a method getWidth() with a return value string.
Working with interfaces
Now that we know how to declare interfaces, let’s see them in action for a few common use cases.
Optional Properties
In the previous code sample, all properties in the interface are required. If we create a new object with that interface and we leave out a property, the TypeScript compiler will throw an error.
There are certain cases, however, where we would expect our objects to have properties that are optional. We can achieve this by placing a question mark (?) just before the property’s type annotation when declaring the interface:
interface Post {title: string;content: string;tags?: string[];}
In this particular scenario, we tell the compiler that it is possible to have Post objects without tags.
It is very useful to describe interfaces in this way, especially if you want to prevent the use of properties not included in the interface.
Read-only properties
It is also possible to mark a property in an interface as read-only. We can do this by adding the readonly keyword before a property’s key:
interface FulfilledOrder {itemsInOrder: number;totalCost: number;readonly dateCreated: Date;}
A read-only property can be assigned during initialization but cannot be reassigned afterward. If you try to reassign its value, the compiler will report an error.
interface FulfilledOrder {itemsInOrder: number;totalCost: number;readonly dateCreated: Date;}interface FulfilledOrder {itemsInOrder: number;totalCost: number;readonly dateCreated: Date;}const order: FulfilledOrder = {itemsInOrder: 1,totalCost: 199.99,dateCreated: new Date(),};order.dateCreated = new Date(2021, 10, 29);
This behavior is conceptually similar to
const, thoughreadonlyapplies to object properties rather than variable bindings.
Function and class types
Interfaces can also be used to describe function types, and to check the structure of JavaScript classes.
To describe a function type with an interface, we give the interface a call signature. This is like a function declaration with only the parameter list and return type given.
interface SumFunc {(a: number, b: number): number;}const add: SumFunc = (a, b) => {return a + b;}
Note: We did not need to specify the parameter type when creating our function because it was inferred through contextual typing from the interface.
The names of the parameters in your function and function type do not need to match. The compiler only checks the types of your function parameters, one at a time, in each corresponding parameter position.
We can also use interfaces to properly type classes created in JavaScript. To achieve this, we create an interface that contains the class’ properties and methods, then we use the implements keyword when creating our class.
interface CarInterface {model: string;year: string;getUnitsSold(): number;}class Car implements CarInterface {model: string;year: string;getUnitsSold() {// logic to return number of units soldreturn 100;}constructor(model: string, year: string) {this.model = model;this.year = year;}}
Note: Our interfaces describe the public side of the class rather than both the public and private side.
Generic interfaces
Sometimes you might not know the type of data that each property in your interface would contain. The easy solution would be to just add the any type to such properties. However, doing so would make our code less type-safe.
TypeScript lets you compose generic interfaces to help with scenarios like these. Generic interfaces are written like normal interfaces, but they allow you to set one or more parameters that can be included in the interface’s definition.
These parameters can be replaced with a type or another interface later in your code.
We can create a generic interface by passing type parameters, using angle brackets (<>), to our interface.
interface PaginatedResponse<T> {data: T[];nextPageUrl?: string;previousPageUrl?: string;}interface Post {title: string;content: string;tags?: string[];}function loadDataFromApi() {fetch('/some/api/endpoint').then((response) => response.json()).then((data: PaginatedResponse<Post>) => console.log(data));}
The above example creates the PaginatedResponse interface that we can reuse to type the data returned from an API source. This can be reused across your code for different API resources with the same basic structure.
Indexable types
Akin to how interfaces are utilized to define certain function types, you can also describe types that can be indexed into. Indexable types each have a unique “index signature” that expresses the types we can use to register into the object, along with the matching returns when indexing. Take the code example of indexable types below:
interface StringArray {[index: number]: string;}let myArray: StringArray;myArray = ["Joe", "Matt"];let myStr: string = myArray[0];
Generally, there are three supported index signature key types: string, number, and symbol. When both string and number indexers are present, the return type of the numeric indexer must be assignable to the return type of the string indexer.
Record, mapped types, and utility helpers with interfaces
Interfaces play nicely with built-ins:
Record<K, V> for dictionary shapes:
interface Product { id: string; title: string }type ProductMap = Record<string, Product>;const byId: ProductMap = { 'p1': { id: 'p1', title: 'Hat' } }
Utility types to transform interface members:
interface Profile { id: string; name?: string; email: string }type ProfileRequired = Required<Profile>;type PublicProfile = Pick<Profile, 'id' | 'name'>;type PrivateProfile = Omit<Profile, 'id'>;type ReadonlyProfile = Readonly<Profile>;
Mapped types to create variants:
type Nullable<T> = { [K in keyof T]: T[K] | null }type NullableProfile = Nullable<Profile>
These patterns help scale large codebases while keeping shared TypeScript type definitions consistent.
Extending an interface
Just like classes, an interface can extend another interface. This allows you to copy members of one interface into another making them reusable. This gives you far more flexibility.
interface PetsInterface {name: string;}interface DogInterface extends PetsInterface {breed: string;}interface CatInterface extends PetsInterface {breed: string;}
An interface can extend a single interface, multiple interfaces, or a class (inheriting only its public instance members), using the extends keyword. We can extend multiple interfaces when they are separated by commas.
To implement both our Dog and Cat interfaces, we also implement the Pets interface.
interface PetsInterface {name: string;}interface DogInterface extends PetsInterface {breed: string;}interface CatInterface extends PetsInterface {breed: string;}class Cat implements CatInterface {name: string = 'Garfield';breed: string = 'Tabby';}class Dog implements DogInterface {name: string = 'Rover';breed: string = 'Poodle';}
Best practices & pitfalls with TypeScript interfaces
Prefer methods for behaviors that rely on
this; arrow functions are often used for callbacks due to their lexicalthisbinding.
interface Timer { start(): void } // good: method binds `this` properly
Optional vs possibly-undefined:
name?: stringmeans “may be omitted”;name: string | undefinedmeans “present but can be undefined.” Choose intentionally.Readonly for safety: Use readonly in Typescript interfaces for props/DTOs to prevent accidental reassignment; combine with
Readonly<T>for shallow, compile-time immutability.Narrow public contracts: Keep interface members minimal and stable; expose unions for states instead of booleans that drift over time.
Don’t over-any: If flexibility is needed, prefer unknown, generics with constraints, or index signatures over any.
What to learn next
Congratulations on making it to the end! Interfaces are a powerful tool that make errors easier to detect at compile-time. TypeScript is a powerful tool that makes complex JavaScript projects easier. In fact, TypeScript brings many other features to help you write maintainable and reusable code.
Some of these features that you should learn next include:
Utility Types
Union and Intersection Types
Enums
Type Guards
Type Aliases
To learn these tools and advance your TypeScript skills, check out Educative’s curated learning path TypeScript for Front-End Developers. This path will allow you to painlessly transition your JavaScript experience to TypeScript. By the end, you’ll know how to use advanced TypeScript in professional projects.Whether you’re new to TypeScript or looking to advance your skills, this is your one-stop-Typescript-shop.Happy learning!