Interfaces

December 2, 20258 min read
Requirements:
FunctionsObjects

Interfaces in TypeScript: Defining Object Shapes

A TypeScript interface looks simple at first glance, yet many newcomers wonder when to use one and why it matters. If you have worked with plain JavaScript objects for years, the idea of giving those shapes a name can feel like an extra chore. This guide clears up that confusion and shows how interfaces make your code safer and easier to read.

Think of an interface as a small form with labeled fields. Any object that fills in the required fields counts as valid. No magic. No ceremony. You describe the shape and TypeScript checks that your data fits the shape. This alone prevents many silent mistakes in larger projects.

What a TypeScript Interface Actually Is

An interface is a named shape for an object. It lists the properties an object must have and the types of those properties. If an object matches the required structure, TypeScript accepts it. The compiler uses structural typing which means that the object does not need to state that it implements the interface. It simply needs to match it by shape.

Here is a tiny example to ground the concept.

interface Person {
  name: string
  age: number
}
 
function greet(p: Person) {
  console.log(`Hello ${p.name}`)
}

Any object with a name and age fits this shape. If one field is missing, TypeScript warns you early which saves debugging time.

Why Interfaces Matter in Daily Code

Every time you pass data around in an app you rely on assumptions about shape. Interfaces make those assumptions explicit. When another developer reads a function like loadUser with parameter user of type User, they instantly know what structure to expect. This makes communication and collaboration easier.

Interfaces also allow you to reuse shapes across many parts of the codebase instead of repeating object definitions everywhere. This keeps things simple and prevents drift between different parts of a system.

Extending Interfaces

One of the strongest features of interfaces is that you can extend them. This lets you build larger shapes from smaller ones, similar to how intersection types combine types with &. You define a base and layer on new pieces without repeating old fields.

interface BasicInfo {
  name: string
  age: number
}
 
interface Address {
  city: string
  country: string
}
 
interface Employee extends BasicInfo, Address {
  id: number
  company: string
}

An interface can extend as many parents as you like. Employee above pulls in every field from both BasicInfo and Address, then adds two of its own. If two parents declare the same property with incompatible types, TypeScript rejects the extension instead of silently picking a winner.

This makes your type system modular and easy to maintain. Any update to the base shapes flows through to all extended shapes.

Optional and Readonly Fields

Real world data is rarely complete and some values must not change after creation. Interfaces support this through optional fields and readonly fields. Optional fields allow flexibility. Readonly fields protect important values like ids from accidental updates.

interface AppConfig {
  readonly version: string
  themeColor?: string
}

This gives your shapes more real world accuracy. When you need to flip every field to optional or readonly at once, do not hand-write a second interface. Mapped types such as Partial and Readonly derive the new shape from the old one for you.

Index Signatures for Dynamic Keys

Sometimes you do not know the property names ahead of time. An index signature describes the key type and the value type instead of listing each field.

interface FeatureFlags {
  [flag: string]: boolean
}
 
const flags: FeatureFlags = {
  darkMode: true,
  betaSearch: false,
}

Any string key is now allowed as long as its value is a boolean. If your keys come from a known set rather than open-ended input, reach for Record instead — it keeps the key list checked, which an index signature cannot do.

Describing Functions and Constructors

Interfaces are not limited to plain data. A call signature lets an interface describe a callable value.

interface Formatter {
  (value: number): string
}
 
const currency: Formatter = (value) => `$${value.toFixed(2)}`

Note the missing method name. That bare (value: number): string says the value itself is callable. Add regular properties alongside it and you can describe a function that also carries metadata.

A construct signature does the same job for classes, using new.

interface Widget {
  id: string
}
 
interface WidgetConstructor {
  new (id: string): Widget
}
 
function createWidget(ctor: WidgetConstructor, id: string): Widget {
  return new ctor(id)
}

This is how you type a factory that receives a class and instantiates it rather than receiving an instance.

Generic Interfaces

Hard-coding one concrete type into an interface stops working the moment you need the same shape for something else. A type parameter fixes that.

interface ApiResponse<T> {
  data: T
  status: number
}
 
const users: ApiResponse<string[]> = {
  data: ['ada', 'grace'],
  status: 200,
}

One interface now covers every response your API returns. See generics for the full picture of type parameters and constraints.

Declaration Merging

One unique feature of interfaces is declaration merging. If you declare the same interface twice, TypeScript combines them into one. This is useful when extending third party libraries or adding fields incrementally.

interface User {
  id: number
}
 
interface User {
  name: string
}
 
// User now has both id and name
const user: User = { id: 1, name: 'Alice' }

Type aliases do not support this. Declaring the same type twice causes an error. This makes interfaces the better choice when you need to augment existing definitions.

Implementing Interfaces in Classes

Interfaces pair naturally with classes. The implements keyword ensures a class provides all required properties and methods.

interface Printable {
  print(): void
}
 
class SalesReport implements Printable {
  print() {
    console.log('Printing report...')
  }
}

If the class misses a required member, TypeScript warns you immediately. This pattern is common in larger codebases where you want to enforce contracts across different implementations.

Keep in mind that implements only checks the class. It does not change the types of the members you write, so each method still needs its own parameter annotations.

Interfaces vs Types

Type aliases and interfaces often overlap but have different strengths. Interfaces support extension and declaration merging which can be useful for evolving systems. Types shine when you need unions or advanced type features. A simple rule is to reach for interfaces when modeling plain object shapes and reach for types when you need more complex compositions.

Three cases settle the choice quickly:

This contrast helps beginners choose without overthinking. Interfaces are great for simple shapes that may grow. Types work better for tricky logic or variant structures.

A Tiny Template for Everyday Use

Here is a small pattern that keeps your code clean.

interface ModelBase {
  id: string
  createdAt: Date
}
 
interface UserModel extends ModelBase {
  email: string
}

Use a base interface for shared fields and extend it for specific models. This keeps duplication low and clarity high.

Common Mistakes

Watch out for these pitfalls when working with interfaces:

A quick debugging tip: hover over your interface in the editor to see the fully merged type. This helps catch missing or conflicting fields early.

Wrap Up

Interfaces help you name shapes, communicate intent, and catch errors early. They keep code readable and give structure to the objects you work with every day. Start small. Add one interface to describe a common object in your project and notice how your code becomes clearer immediately.

Have you run into any tricky interface patterns or unexpected behavior? Share your experience with us. We would love to hear how you use interfaces in your projects.

Share this article

Become a TypeScript Pro

Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges

Practice with Challenges

Put your interfaces knowledge to the test with these related challenges.

#599Merge
Medium
#27932MergeAll
Medium
#29650ExtractToObject
Medium
#1978Percentage Parser
Medium

Related Concepts

Concepts that build on or relate to interfaces.

Intersection TypesMapped Types

Common Errors

TypeScript errors you might encounter when working with interfaces.

TS2300Fix TS2300: Duplicate IdentifierTS2314Fix TS2314: Generic Type Requires Type ArgumentsTS2322Fix TS2322: Type Not AssignableTS2339Fix TS2339: Property Does Not Exist on TypeTS2416Fix TS2416: Property Not Assignable to Base TypeTS2503Fix TS2503: Cannot Find NamespaceTS2551Fix TS2551: Property Does Not Exist - Did You Mean?TS2693Fix TS2693: Only Refers to a Type, Used as a Value