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
| Feature | Interface | Type Alias |
|---|---|---|
| Object shape | Yes | Yes |
| Extension | extends keyword | & intersection |
| Merging | Supported (declaration merging) | Not supported |
| Primitives | Not directly | Yes (type ID = number) |
| Unions | Not directly | Yes (type A = B | C) |
| Best use | Object and class shapes | Aliases, 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
