TypeScript Interfaces

An interface defines a contract — a formal blueprint that describes the shape an object must follow. Any object, class, or function that claims to implement an interface must provide all the required properties and methods declared in it. Interfaces keep code consistent and make large applications easier to maintain by standardizing how data looks throughout the program.

Interface vs Inline Object Type

  Inline Object Type (one-time use):
  let user: { name: string; age: number } = { ... };

  Interface (reusable across the codebase):
  interface User {
      name: string;
      age: number;
  }
  let user1: User = { ... };
  let user2: User = { ... };
  let user3: User = { ... };

An interface acts as a named template. Write it once and reuse it everywhere a consistent shape is needed.

Defining an Interface

interface Student {
    studentId: number;
    name: string;
    age: number;
    grade: string;
}

Using an Interface

interface Student {
    studentId: number;
    name: string;
    age: number;
    grade: string;
}

let s1: Student = {
    studentId: 101,
    name: "Farhan",
    age: 19,
    grade: "A"
};

let s2: Student = {
    studentId: 102,
    name: "Bhavna",
    age: 20,
    grade: "B+"
};

console.log(s1.name + " | Grade: " + s1.grade); // Farhan | Grade: A
console.log(s2.name + " | Grade: " + s2.grade); // Bhavna | Grade: B+

Optional Properties in Interfaces

interface Employee {
    empId: number;
    name: string;
    department: string;
    email?: string;     // optional
    phone?: string;     // optional
}

let emp1: Employee = {
    empId: 201,
    name: "Sunita Rao",
    department: "HR"
    // email and phone are optional — omitting them is fine
};

let emp2: Employee = {
    empId: 202,
    name: "Vinod Kumar",
    department: "IT",
    email: "vinod@company.com"
};

Readonly Properties in Interfaces

interface BankAccount {
    readonly accountNumber: string;  // cannot change after creation
    holderName: string;
    balance: number;
}

let account: BankAccount = {
    accountNumber: "ACC-90123",
    holderName: "Madhuri Patel",
    balance: 25000
};

account.balance = 30000;           // Allowed
account.accountNumber = "ACC-99"; // Error: Cannot assign to 'accountNumber' (read-only)

Methods in Interfaces

Interfaces can define method signatures — the name, parameters, and return type of a function — without providing the actual implementation.

interface Calculator {
    add(a: number, b: number): number;
    subtract(a: number, b: number): number;
    multiply(a: number, b: number): number;
}

let calc: Calculator = {
    add(a, b) { return a + b; },
    subtract(a, b) { return a - b; },
    multiply(a, b) { return a * b; }
};

console.log(calc.add(10, 5));      // 15
console.log(calc.subtract(10, 5)); // 5
console.log(calc.multiply(10, 5)); // 50

Extending Interfaces

One interface can extend another, inheriting all its properties and adding more. This builds a hierarchy of related shapes without repeating definitions.

  Base Interface          Extended Interface
  +-----------+           +-----------+
  | Person    |           | Student   |
  |-----------|  extends  |-----------|
  | name      | --------> | name      |  (inherited)
  | age       |           | age       |  (inherited)
  +-----------+           | rollNo    |  (new)
                          | grade     |  (new)
                          +-----------+
interface Person {
    name: string;
    age: number;
}

interface Student extends Person {
    rollNo: number;
    grade: string;
}

let student: Student = {
    name: "Tanvi",  // from Person
    age: 18,        // from Person
    rollNo: 45,     // from Student
    grade: "A+"     // from Student
};

console.log(student.name + " | Roll: " + student.rollNo);
// Output: Tanvi | Roll: 45

Extending Multiple Interfaces

interface Flyable {
    fly(): void;
}

interface Swimmable {
    swim(): void;
}

interface Duck extends Flyable, Swimmable {
    quack(): void;
}

let duck: Duck = {
    fly()   { console.log("Duck is flying"); },
    swim()  { console.log("Duck is swimming"); },
    quack() { console.log("Quack!"); }
};

duck.fly();   // Duck is flying
duck.swim();  // Duck is swimming
duck.quack(); // Quack!

Interface for Function Types

interface MathOperation {
    (x: number, y: number): number;
}

let divide: MathOperation = (x, y) => x / y;
let power: MathOperation = (x, y) => Math.pow(x, y);

console.log(divide(20, 4)); // 5
console.log(power(2, 8));   // 256

Interface Merging

Declaring an interface with the same name twice causes TypeScript to merge both declarations into one. This is unique to interfaces — type aliases (covered separately) do not support merging.

interface Vehicle {
    brand: string;
    speed: number;
}

interface Vehicle {
    fuelType: string;  // merged into Vehicle
}

// TypeScript sees both declarations as one merged interface
let car: Vehicle = {
    brand: "Tata",
    speed: 180,
    fuelType: "Electric"  // required from the second declaration
};

Interface vs Type Alias Quick Comparison

FeatureInterfaceType Alias
Object shapeYesYes
Extensionextends keyword& intersection
MergingSupported (declaration merging)Not supported
PrimitivesNot directlyYes (type ID = number)
UnionsNot directlyYes (type A = B | C)
Best useObject and class shapesAliases, unions, complex types

Practical Example

// E-commerce product interface
interface Product {
    readonly productId: number;
    name: string;
    price: number;
    category: string;
    inStock: boolean;
    discount?: number;
    getDiscountedPrice(): number;
}

let laptop: Product = {
    productId: 5001,
    name: "Dell Laptop",
    price: 65000,
    category: "Electronics",
    inStock: true,
    discount: 10,
    getDiscountedPrice() {
        let disc = this.discount ?? 0;
        return this.price - (this.price * disc / 100);
    }
};

console.log(laptop.name + " | Price: ₹" + laptop.price);
console.log("After Discount: ₹" + laptop.getDiscountedPrice());
// Output:
// Dell Laptop | Price: ₹65000
// After Discount: ₹58500

Leave a Comment