Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably seen type and interface used interchangeably in a dozen tutorials, and honestly, for about 90% of your daily work, they'll behave exactly the same. This leads to a very common misconception: that an interface is just a slightly different syntax for a type alias. If you think of them as the same thing, you're missing the actual architectural purpose of the interface.

The "Interface is just a Type Alias" Identity Crisis

If you define a type User = { name: string } and an interface User { name: string }, they both describe the same object shape. But here is where that logic falls apart: types are closed, while interfaces are open.

Take a look at this. I'm pretending I'm using a third-party library that defines a Window object. If that library used a type, I'd be stuck with whatever they gave me. But because it's an interface, I can do this:

// Imagine this is inside a library you didn't write
interface Window {
  title: string;
}

// In your own code, you can just... add to it.
interface Window {
  customPluginId: string;
}

const myWindow: Window = {
  title: "My App",
  customPluginId: "abc-123"
};

This is called declaration merging. TypeScript sees two interfaces with the same name in the same namespace and merges them into one. You cannot do this with types. If you try to declare type Window twice, the compiler will scream at you. This is why interfaces are the gold standard for library authors and large-scale architectures; they allow others to extend the "contract" without modifying the original source code.

Defining a Contract, Not Just a Shape

I like to think of interfaces as a contract. When I use an interface, I'm not just saying "this object has these fields"; I'm saying "any class or object that claims to be a PaymentProcessor must fulfill these requirements."

This becomes powerful when you use the implements keyword with classes. It forces the class to adhere to the interface, which is a lifesaver when you're working in a team and want to ensure that different developers are building compatible modules.

interface Logger {
  log(message: string): void;
  error(message: string): void;
}

// This class is now contractually obligated to have log() and error()
class CloudLogger implements Logger {
  log(message: string) {
    console.log(`Sending to cloud: ${message}`);
  }
  
  error(message: string) {
    console.error(`Cloud Alert: ${message}`);
  }
}

If I forget to implement error() in the CloudLogger class, TypeScript will flag it immediately. I don't have to wait until runtime to find out that a method is missing.

Read-only and Optionality in Contracts

Since interfaces are often used for data coming from APIs or configuration files, you'll frequently use the readonly and ? modifiers. I use readonly religiously for things that should never change after the object is created—like a user's unique ID.

interface APIResponse {
  readonly status: number; // Cannot be changed after initialization
  data: any;
  errorMessage?: string; // Optional: might not be there if the request succeeded
}

By marking status as readonly, I prevent some future version of myself (or a teammate) from accidentally overwriting the response code halfway through a function. It turns a potential runtime bug into a compile-time error.




📋 Practical Task

Building a Plugin-Based Notification System

You are building a notification system that needs to support different channels (Email, SMS, Slack). You need to ensure that every new channel added to the system follows the exact same structure so the main NotificationManager can use them interchangeably.

Your Task:

  • Create an interface named NotificationChannel that requires a send(message: string): boolean method and a readonly channelName: string property.
  • Create a class EmailChannel that implements NotificationChannel. In the send method, log "Sending Email: [message]" and return true.
  • Create a class SmsChannel that implements NotificationChannel. In the send method, log "Sending SMS: [message]" and return true.
  • Write a function dispatchNotification(channel: NotificationChannel, msg: string) that calls the send method of the provided channel.
  • Instantiate both classes and pass them into dispatchNotification to verify they work.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.