No history yet

Interfaces

Defining Contracts with Interfaces

In TypeScript, an interface is a way to define a contract for an object's shape. Think of it as a blueprint. It specifies what properties an object should have and what types those properties should be. This helps catch errors early and makes your code much easier to understand and work with.

Interfaces in TypeScript allow you to define the shape of objects and specify the types of their properties.

Let's start by declaring a simple interface for a user object.

interface User {
  id: number;
  name: string;
  email: string;
}

// This object correctly follows the User interface
const myUser: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
};

// This object will cause an error!
// Property 'email' is missing.
const anotherUser: User = {
    id: 2,
    name: "Bob"
};

The User interface dictates that any object of type User must have an id that's a number, a name that's a string, and an email that's a string. If you try to create a User object that's missing a property or has a property of the wrong type, TypeScript will immediately flag it as an error. This enforcement is the core power of interfaces.

Flexible Contracts

Sometimes, not all properties on an object are required. You can mark properties as optional by adding a question mark ? after the property name.

Optional properties give you flexibility while still providing type safety for the properties that do exist.

You might also want to prevent a property from being changed after an object is created. For this, TypeScript provides the readonly modifier.

interface Profile {
  readonly id: number;
  username: string;
  bio?: string; // bio is optional
}

const userProfile: Profile = {
  id: 101,
  username: "coder_cat",
};

// This is fine, we can add the optional property later
userProfile.bio = "Loves to code and nap."; 

// This will cause an error!
// Cannot assign to 'id' because it is a read-only property.
userProfile.id = 102;

In this example, bio is optional, so a Profile object is valid with or without it. The id, however, is read-only. Once it's set, you can't change its value.

Interfaces can also describe the shape of functions. This is useful when you want to ensure that a function signature is consistent across different parts of your application.

interface StringFormatter {
  (str: string, isUpperCase: boolean): string;
}

let format: StringFormatter;

format = function (str, isUpperCase) {
  return isUpperCase ? str.toUpperCase() : str.toLowerCase();
};

console.log(format("Hello World", true)); // Outputs: HELLO WORLD

Here, the StringFormatter interface ensures that any function assigned to the format variable must accept a string and a boolean, and must return a string.

Building on Interfaces

Interfaces are composable. You can create a new interface that extends an existing one, inheriting its properties. This helps keep your code DRY (Don't Repeat Yourself).

// Our original User interface
interface User {
  id: number;
  name: string;
}

// Admin extends User, inheriting id and name
interface Admin extends User {
  accessLevel: number;
}

const siteAdmin: Admin = {
  id: 1,
  name: "Superuser",
  accessLevel: 5,
};

The Admin interface has all the properties of User, plus its own accessLevel property. This is a clean way to build up more complex types from simpler ones.

Finally, interfaces are crucial for working with classes. A class can implement an interface, which is a promise that the class will adhere to the structure defined by that interface. If the class fails to meet the contract, TypeScript will report an error.

interface Printable {
  print(): void; // A method that takes no arguments and returns nothing
}

class Report implements Printable {
  title: string;

  constructor(title: string) {
    this.title = title;
  }

  print() {
    console.log(`Printing report: ${this.title}`);
  }
}

const myReport = new Report("Quarterly Sales");
myReport.print(); // Works perfectly

By implementing Printable, the Report class guarantees it will have a print method that matches the interface's signature. This creates predictable and reliable components in your code.

Now, let's review the key terms we've discussed.

Ready to check your understanding?

Quiz Questions 1/5

What is the primary purpose of a TypeScript interface?

Quiz Questions 2/5

Which snippet correctly defines an optional middleName property of type string within an interface?

Mastering interfaces is a big step toward writing professional, scalable TypeScript code. They provide structure and safety, making your applications easier to build and maintain.