Interfaces in TypeScript Explained

TypeScript interfaces are named contracts that describe the shape an object must have. Learn how to define them, how structural typing works, and how they support extension and merging.

6 min read

TypeScript interfaces are named descriptions of an object's shape. An interface lists the properties an object must have and the types those properties must be. You define an interface once and use it everywhere that shape is expected.

Think of an interface as a contract. Any object that has the properties listed in the interface satisfies the contract. The object can have extra properties too. TypeScript only checks that the required ones are present and have the right types.

Defining an interface

An interface declaration names a shape once so every function and variable can reuse it instead of repeating an inline object type. Use the interface keyword, followed by a name and a block of property declarations.

typescripttypescript
interface User {
  name: string;
  age: number;
}
 
function greet(user: User) {
  console.log(`Hello, ${user.name}.`);
}
 
const ada: User = { name: "Ada", age: 30 };
greet(ada);

The User interface says: any User must have a name of type string and an age of type number. The compiler checks the ada variable at the assignment and again at the function call.

Interfaces can include optional properties with a question mark and readonly properties with the readonly keyword. For example, a Config interface might have a required name, an optional timeout, and a readonly id that cannot change after creation.

Interfaces and structural typing

TypeScript uses structural typing, sometimes called duck typing: if it looks like a duck and quacks like a duck, it is a duck. The compiler only checks the shape of the value. It does not care whether the value was explicitly declared as implementing the interface.

typescripttypescript
interface Labeled {
  label: string;
}
 
function printLabel(obj: Labeled) {
  console.log(obj.label);
}
 
const item = { label: "Milk", price: 2.5, inStock: true };
printLabel(item); // "Milk"

The item variable was not declared as Labeled. It was inferred as an object with label, price, and inStock. But it has a label of type string, so it satisfies the Labeled contract. TypeScript allows it.

This is different from languages like Java or C#, where a class must explicitly declare that it implements an interface. In TypeScript, no explicit declaration is needed. The shape alone is enough.

Extending interfaces

Interfaces can extend other interfaces. The extending interface inherits all properties of the base and adds its own.

typescripttypescript
interface Animal {
  name: string;
}
 
interface Dog extends Animal {
  breed: string;
}
 
const rex: Dog = { name: "Rex", breed: "Golden Retriever" };

Dog extends Animal, so it requires both name from Animal and breed from itself. You can extend multiple interfaces at once, combining their contracts into a single interface. The resulting interface requires all properties from all parent interfaces, plus any properties declared in its own body.

Extension is a way to compose interfaces from smaller, focused pieces. Instead of one large interface with every property, break it into reusable contracts and combine them with extends.

Declaration merging

One unique feature of interfaces is declaration merging. If you declare the same interface name twice in the same scope, TypeScript merges the declarations into a single interface with all properties combined.

typescripttypescript
interface Window {
  title: string;
}
 
interface Window {
  version: number;
}
 
const win: Window = { title: "My App", version: 2 };

The merged Window requires both title and version. This is commonly used to augment built-in types or third-party library types without modifying the original source. Type aliases cannot do this -- trying to declare the same type alias twice produces a duplicate identifier error.

Interfaces vs inline object types

You can describe an object's shape inline without naming it. This works for a single function parameter, but repeating the inline type across multiple functions is error-prone.

Interfaces give the shape a name, which makes the code more readable and easier to change. Interfaces also appear by name in error messages. An error that says "missing property Y from type User" is much clearer than one that prints the full anonymous type.

Inline object typeInterface
Reusable across filesNo, must be retypedYes, imported by name
Appears in error messagesAs a full anonymous shapeBy name
Extendable with extendsNoYes

For more on the differences between interfaces and the type keyword, see interface vs type in TypeScript. Also see type aliases in TypeScript explained for the other side of the naming story.

When to use interfaces

Use an interface when you are describing the shape of an object, when you want the type to be extendable later, when you want the type name to appear in error messages, and when you are building a public API where consumers may need to augment types.

Use a type alias instead when you need to describe a union, intersection, tuple, or mapped type. Interfaces can only describe object shapes.

Common mistakes

Forgetting that extra properties are allowed. An object with more properties than the interface requires is still valid, as long as it is not an object literal passed directly to a typed position. The excess property check only applies to literals.

Expecting an implements relationship between objects and interfaces. TypeScript does not require a declaration. If an object has the right properties with the right types, it satisfies the interface. This is structural typing, and it is by design.

Trying to use extends on a type alias. extends only works on interfaces. To combine type aliases, use intersection types with the ampersand operator.

Rune AI

Rune AI

Key Insights

  • An interface defines a named contract that objects satisfy by having the right properties.
  • TypeScript uses structural typing: an object satisfies an interface if it has the required properties.
  • Extend interfaces with extends to build new contracts from existing ones.
  • Interfaces support declaration merging: declaring the same name twice merges the properties.
  • Interfaces always appear by name in error messages, which helps readability.
RunePowered by Rune AI

Frequently Asked Questions

Can interfaces describe function types?

Yes. An interface can have a call signature: `interface SearchFn { (source: string, query: string): boolean }`. The call signature describes a function's parameter types and return type without a property name.

Can I add properties to an existing interface?

Yes, through declaration merging. If you declare the same interface name twice in the same scope, TypeScript merges the declarations. This is commonly used to augment built-in or third-party library types.

Do interfaces exist at runtime?

No. Like all TypeScript types, interfaces are erased during compilation. They only exist to help the compiler check your code. The compiled JavaScript contains no trace of interface declarations.

Conclusion

An interface is a named contract for object shapes. It describes what properties an object must have and their types. Interfaces support extension through the extends keyword and can participate in declaration merging. Use interfaces when you want a named, reusable, and extendable description of an object's structure.