Prerequisites
- Basic TypeScript types knowledge ๐
- Understanding of objects in JavaScript โก
- Functions and parameters basics ๐ป
What you'll learn
- Create and use interfaces effectively ๐ฏ
- Understand optional and readonly properties ๐๏ธ
- Master interface inheritance and extension ๐
- Apply interfaces in real-world scenarios โจ
๐ฏ Introduction
Welcome to the world of TypeScript interfaces! ๐ If types are the atoms of TypeScript, interfaces are the molecules - they combine simple types into powerful structures.
Think of interfaces as blueprints for objects ๐๏ธ. Just like an architectโs blueprint ensures every room has doors and windows in the right places, interfaces ensure your objects have the right properties with the right types. Theyโre contracts that say โif you want to be this type of thing, you must have these properties!โ
By the end of this tutorial, youโll be creating interfaces like a pro, making your code more predictable, maintainable, and delightful to work with! Letโs build! ๐โโ๏ธ
๐ Understanding Interfaces
๐ค What Are Interfaces?
Interfaces define the shape of objects in TypeScript. Theyโre like a checklist ๐ that objects must satisfy to be considered a certain type.
Unlike classes (which are blueprints for creating objects), interfaces are purely for type-checking. They exist only at compile time and disappear in the JavaScript output:
- โจ Define object structures
- ๐ Enable type checking
- ๐ก๏ธ Provide IntelliSense support
- ๐ Document your codeโs expectations
๐ก Interface vs Type Alias
Both can describe object shapes, but interfaces have superpowers:
// ๐ฏ Interface - preferred for objects
interface User {
name: string;
age: number;
}
// ๐จ Type alias - more flexible
type UserType = {
name: string;
age: number;
};
// ๐ก Key differences:
// 1. Interfaces can be extended and merged
interface User {
email?: string; // This merges with the above!
}
// 2. Interfaces are better for OOP
class Employee implements User {
name = "Alice";
age = 30;
email = "[email protected]";
}
// 3. Type aliases can represent primitives and unions
type ID = string | number; // Can't do this with interface
๐ง Basic Interface Syntax
๐ Creating Your First Interface
Letโs start with simple interfaces:
// ๐จ Basic interface structure
interface Person {
firstName: string;
lastName: string;
age: number;
}
// โ
Object that matches the interface
const student: Person = {
firstName: "Emma",
lastName: "Watson",
age: 25
};
// โ Missing required property
// const incomplete: Person = {
// firstName: "John",
// age: 30
// }; // Error: Property 'lastName' is missing
// ๐ฏ Using interfaces as function parameters
function greetPerson(person: Person): string {
return `Hello, ${person.firstName} ${person.lastName}! ๐`;
}
console.log(greetPerson(student)); // "Hello, Emma Watson! ๐"
๐จ Optional Properties
Not every property is required - use ?
for optional ones:
// ๐ Real estate listing interface
interface House {
address: string;
bedrooms: number;
bathrooms: number;
garage?: boolean; // Optional - not all houses have garages
pool?: boolean; // Optional - luxury feature
yearBuilt?: number; // Optional - might not know
}
// โ
Valid houses with different optional properties
const cozyHome: House = {
address: "123 Main St",
bedrooms: 3,
bathrooms: 2
// No garage, pool, or yearBuilt - that's OK!
};
const luxuryHome: House = {
address: "456 Ocean View",
bedrooms: 5,
bathrooms: 4,
garage: true,
pool: true,
yearBuilt: 2020
};
// ๐ฏ Function handling optional properties
function describeHouse(house: House): string {
let description = `๐ ${house.address}: ${house.bedrooms}BR/${house.bathrooms}BA`;
if (house.garage) description += " | Garage โ
";
if (house.pool) description += " | Pool ๐";
if (house.yearBuilt) description += ` | Built ${house.yearBuilt}`;
return description;
}
๐ Readonly Properties
Some properties shouldnโt change after creation:
// ๐ Book interface with immutable properties
interface Book {
readonly isbn: string; // Can't change ISBN
readonly author: string; // Can't change author
title: string; // Can change (editions)
price: number; // Can change (sales)
}
const typeScriptBook: Book = {
isbn: "978-1-491-96720-4",
author: "Boris Cherny",
title: "Programming TypeScript",
price: 39.99
};
// โ
Can modify mutable properties
typeScriptBook.price = 29.99; // On sale! ๐
// โ Cannot modify readonly properties
// typeScriptBook.isbn = "123-4-567-89012-3"; // Error!
// typeScriptBook.author = "Someone Else"; // Error!
// ๐ก๏ธ Readonly arrays
interface TeamRoster {
readonly players: readonly string[]; // Double readonly!
}
const basketballTeam: TeamRoster = {
players: ["LeBron", "Curry", "Durant", "Giannis", "Jokic"]
};
// โ Can't reassign or modify
// basketballTeam.players = ["New", "Players"]; // Error!
// basketballTeam.players.push("New Player"); // Error!
๐ก Advanced Interface Features
๐ฏ Index Signatures
When you donโt know all property names ahead of time:
// ๐ Dynamic configuration interface
interface Config {
apiUrl: string; // Known property
timeout: number; // Known property
[key: string]: string | number; // Any other string key!
}
const appConfig: Config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retryAttempts: 3, // Dynamic property
environment: "production", // Dynamic property
maxConnections: 100 // Dynamic property
};
// ๐ฎ Game scores with dynamic player names
interface GameScores {
gameName: string;
[playerName: string]: string | number; // Player names as keys
}
const arcadeScores: GameScores = {
gameName: "Space Invaders",
"Alice": 15000,
"Bob": 12000,
"Charlie": 18000,
"ๆฐใใใใฌใคใคใผ": 20000 // Any string works!
};
// ๐ก Accessing dynamic properties
for (const player in arcadeScores) {
if (player !== "gameName") {
console.log(`${player}: ${arcadeScores[player]} points ๐ฎ`);
}
}
๐๏ธ Extending Interfaces
Build complex interfaces from simple ones:
// ๐งฑ Base interfaces
interface Animal {
name: string;
age: number;
}
interface Mammal extends Animal {
furColor: string;
numberOfLegs: number;
}
interface Pet extends Animal {
owner: string;
isVaccinated: boolean;
}
// ๐ Combining multiple interfaces
interface Dog extends Mammal, Pet {
breed: string;
goodBoy: boolean; // Always true! ๐ฅฐ
}
const myDog: Dog = {
// From Animal
name: "Buddy",
age: 3,
// From Mammal
furColor: "golden",
numberOfLegs: 4,
// From Pet
owner: "Sarah",
isVaccinated: true,
// Dog-specific
breed: "Golden Retriever",
goodBoy: true
};
// ๐ข Real-world example: Employee hierarchy
interface Person {
firstName: string;
lastName: string;
email: string;
}
interface Employee extends Person {
employeeId: string;
department: string;
hireDate: Date;
}
interface Manager extends Employee {
teamMembers: Employee[];
budget: number;
}
// ๐ฏ Creating a manager
const techLead: Manager = {
firstName: "Alex",
lastName: "Chen",
email: "[email protected]",
employeeId: "EMP001",
department: "Engineering",
hireDate: new Date("2020-01-15"),
teamMembers: [],
budget: 500000
};
๐ง Function Types in Interfaces
Interfaces can describe functions too:
// ๐ฏ Function type interface
interface MathOperation {
(x: number, y: number): number;
}
const add: MathOperation = (x, y) => x + y;
const multiply: MathOperation = (x, y) => x * y;
const subtract: MathOperation = (x, y) => x - y;
// ๐งฎ Calculator interface with methods
interface Calculator {
// Method signatures
add(x: number, y: number): number;
subtract(x: number, y: number): number;
multiply(x: number, y: number): number;
divide(x: number, y: number): number;
// Property that's a function
lastOperation: MathOperation | null;
}
class BasicCalculator implements Calculator {
lastOperation: MathOperation | null = null;
add(x: number, y: number): number {
this.lastOperation = add;
return x + y;
}
subtract(x: number, y: number): number {
this.lastOperation = subtract;
return x - y;
}
multiply(x: number, y: number): number {
this.lastOperation = multiply;
return x * y;
}
divide(x: number, y: number): number {
if (y === 0) throw new Error("Division by zero! ๐ซ");
this.lastOperation = (a, b) => a / b;
return x / y;
}
}
// ๐ฎ Event handler interface
interface ClickHandler {
(event: MouseEvent): void;
}
interface Button {
label: string;
onClick: ClickHandler;
onDoubleClick?: ClickHandler; // Optional handler
}
const saveButton: Button = {
label: "Save ๐พ",
onClick: (e) => {
console.log("Saving...");
e.preventDefault();
}
};
๐ Practical Examples
๐ E-commerce Product System
Letโs build a real product management system:
// ๐ท๏ธ Product interfaces
interface Product {
id: string;
name: string;
price: number;
description: string;
inStock: boolean;
}
interface DigitalProduct extends Product {
downloadUrl: string;
fileSize: number; // in MB
license: "single" | "multi" | "enterprise";
}
interface PhysicalProduct extends Product {
weight: number; // in kg
dimensions: {
width: number;
height: number;
depth: number;
};
shippingCost: number;
}
interface Review {
userId: string;
rating: 1 | 2 | 3 | 4 | 5;
comment: string;
date: Date;
helpful: number;
}
interface ProductWithReviews extends Product {
reviews: Review[];
averageRating: number;
}
// ๐๏ธ Shopping cart
interface CartItem {
product: Product;
quantity: number;
}
interface ShoppingCart {
items: CartItem[];
customerId: string;
createdAt: Date;
addItem(product: Product, quantity: number): void;
removeItem(productId: string): void;
getTotalPrice(): number;
checkout(): Order;
}
interface Order {
orderId: string;
items: CartItem[];
totalPrice: number;
status: "pending" | "processing" | "shipped" | "delivered";
orderDate: Date;
}
// ๐๏ธ Implementation
class OnlineShoppingCart implements ShoppingCart {
items: CartItem[] = [];
customerId: string;
createdAt: Date;
constructor(customerId: string) {
this.customerId = customerId;
this.createdAt = new Date();
}
addItem(product: Product, quantity: number): void {
const existingItem = this.items.find(item => item.product.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.items.push({ product, quantity });
}
console.log(`Added ${quantity}x ${product.name} to cart ๐`);
}
removeItem(productId: string): void {
this.items = this.items.filter(item => item.product.id !== productId);
console.log(`Removed item from cart ๐๏ธ`);
}
getTotalPrice(): number {
return this.items.reduce((total, item) => {
return total + (item.product.price * item.quantity);
}, 0);
}
checkout(): Order {
return {
orderId: `ORD-${Date.now()}`,
items: [...this.items],
totalPrice: this.getTotalPrice(),
status: "pending",
orderDate: new Date()
};
}
}
// ๐ฎ Usage
const laptop: PhysicalProduct = {
id: "LAPTOP-001",
name: "TypeScript Developer Laptop",
price: 1299.99,
description: "Perfect for TypeScript development",
inStock: true,
weight: 2.5,
dimensions: { width: 35, height: 25, depth: 2 },
shippingCost: 29.99
};
const course: DigitalProduct = {
id: "COURSE-001",
name: "Master TypeScript Course",
price: 89.99,
description: "Learn TypeScript from zero to hero",
inStock: true,
downloadUrl: "https://downloads.example.com/course",
fileSize: 2048,
license: "single"
};
const cart = new OnlineShoppingCart("CUST-123");
cart.addItem(laptop, 1);
cart.addItem(course, 2);
console.log(`Total: $${cart.getTotalPrice().toFixed(2)} ๐ฐ`);
๐ฎ Game Development Interfaces
Building a game character system:
// ๐ฆธ Character system interfaces
interface Stats {
health: number;
maxHealth: number;
mana: number;
maxMana: number;
strength: number;
defense: number;
speed: number;
}
interface Ability {
name: string;
description: string;
manaCost: number;
cooldown: number;
damage?: number;
healing?: number;
icon: string; // emoji for fun!
}
interface Character {
id: string;
name: string;
level: number;
experience: number;
stats: Stats;
abilities: Ability[];
}
interface Player extends Character {
username: string;
gold: number;
inventory: Item[];
achievements: Achievement[];
}
interface Enemy extends Character {
lootTable: LootDrop[];
aggressionLevel: "passive" | "neutral" | "aggressive";
respawnTime: number;
}
interface Item {
id: string;
name: string;
type: "weapon" | "armor" | "consumable" | "quest";
rarity: "common" | "rare" | "epic" | "legendary";
value: number;
icon: string;
}
interface LootDrop {
item: Item;
dropChance: number; // 0-1
}
interface Achievement {
id: string;
name: string;
description: string;
icon: string;
unlockedAt?: Date;
}
// ๐ฎ Battle system
interface BattleAction {
attacker: Character;
target: Character;
ability: Ability;
execute(): BattleResult;
}
interface BattleResult {
damage?: number;
healing?: number;
statusEffects?: string[];
critical: boolean;
}
// ๐๏ธ Implementation example
class FireballAbility implements BattleAction {
constructor(
public attacker: Character,
public target: Character,
public ability: Ability
) {}
execute(): BattleResult {
const baseDamage = this.ability.damage || 0;
const attackPower = this.attacker.stats.strength;
const defense = this.target.stats.defense;
const damage = Math.max(1, baseDamage + attackPower - defense);
const critical = Math.random() < 0.2; // 20% crit chance
return {
damage: critical ? damage * 2 : damage,
critical,
statusEffects: ["burning"]
};
}
}
// ๐ฏ Create a player
const hero: Player = {
id: "PLAYER-001",
name: "TypeScript Hero",
username: "TSMaster",
level: 10,
experience: 2500,
gold: 1000,
stats: {
health: 100,
maxHealth: 100,
mana: 50,
maxMana: 50,
strength: 15,
defense: 10,
speed: 12
},
abilities: [
{
name: "Fireball",
description: "Launch a blazing fireball",
manaCost: 10,
cooldown: 3,
damage: 25,
icon: "๐ฅ"
},
{
name: "Heal",
description: "Restore health",
manaCost: 15,
cooldown: 5,
healing: 30,
icon: "๐"
}
],
inventory: [],
achievements: []
};
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Interface vs Implementation Confusion
// โ Common mistake - trying to add implementation to interface
interface Calculator {
add(a: number, b: number): number {
return a + b; // Error! Interfaces can't have implementation
}
}
// โ
Correct approach - interface defines shape only
interface Calculator {
add(a: number, b: number): number;
}
// Implementation goes in the class
class BasicCalc implements Calculator {
add(a: number, b: number): number {
return a + b;
}
}
๐คฏ Pitfall 2: Optional Property Misuse
interface User {
name: string;
email?: string;
}
// โ Dangerous assumption
function sendEmail(user: User) {
// This could crash if email is undefined!
console.log(`Sending to ${user.email.toLowerCase()}`);
}
// โ
Safe handling
function sendEmailSafely(user: User) {
if (user.email) {
console.log(`Sending to ${user.email.toLowerCase()} ๐ง`);
} else {
console.log("No email address provided โ ๏ธ");
}
}
๐ต Pitfall 3: Excess Property Checks
interface Point {
x: number;
y: number;
}
// โ Excess property error with object literals
const point3D: Point = {
x: 10,
y: 20,
z: 30 // Error! 'z' does not exist in type 'Point'
};
// โ
Solutions:
// 1. Use type assertion
const point3D = {
x: 10,
y: 20,
z: 30
} as Point;
// 2. Use index signature
interface FlexiblePoint {
x: number;
y: number;
[key: string]: number;
}
// 3. Assign to variable first
const myPoint = { x: 10, y: 20, z: 30 };
const point2D: Point = myPoint; // OK!
๐ ๏ธ Best Practices
๐ฏ Interface Best Practices
-
๐ Use Descriptive Names: Make intent clear
// โ Vague interface Data { } // โ Descriptive interface UserProfile { }
-
๐๏ธ Start Simple, Extend Later: Build incrementally
// Start with basics interface User { id: string; name: string; } // Extend as needed interface AuthenticatedUser extends User { token: string; permissions: string[]; }
-
๐จ Use Readonly for Immutability: Prevent accidental changes
interface Config { readonly apiUrl: string; readonly apiKey: string; }
-
โจ Prefer Interfaces for Object Shapes: More flexible than types
// โ Interface for objects interface Person { name: string; age: number; } // Type for unions/primitives type ID = string | number;
-
๐ง Document Complex Interfaces: Help future developers
/** * Represents a user in our system * @property isActive - Whether the user can log in * @property lastLogin - UTC timestamp of last login */ interface SystemUser { id: string; username: string; isActive: boolean; lastLogin?: Date; }
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Library Management System
Create interfaces for a library system:
๐ Requirements:
- โ Books with ISBN, authors, and availability
- ๐ท๏ธ Members with borrowing limits
- ๐ฅ Borrowing records with due dates
- ๐ Reservation system
- ๐จ Different media types (books, DVDs, audiobooks)
๐ Bonus Points:
- Add late fee calculation
- Implement search functionality
- Create librarian interface with special permissions
๐ก Solution
๐ Click to see solution
// ๐ Base interfaces
interface MediaItem {
id: string;
title: string;
publisher: string;
publishYear: number;
available: boolean;
location: string; // Shelf location
}
interface Book extends MediaItem {
type: "book";
isbn: string;
authors: string[];
pages: number;
genre: string[];
}
interface DVD extends MediaItem {
type: "dvd";
director: string;
duration: number; // minutes
rating: "G" | "PG" | "PG-13" | "R";
}
interface AudioBook extends MediaItem {
type: "audiobook";
narrator: string;
duration: number; // minutes
format: "CD" | "digital";
}
type LibraryItem = Book | DVD | AudioBook;
// ๐ฅ Member interfaces
interface Member {
id: string;
name: string;
email: string;
phone: string;
memberSince: Date;
isActive: boolean;
borrowingLimit: number;
}
interface Student extends Member {
memberType: "student";
studentId: string;
school: string;
}
interface Adult extends Member {
memberType: "adult";
occupation?: string;
}
type LibraryMember = Student | Adult;
// ๐ Transaction interfaces
interface BorrowingRecord {
id: string;
memberId: string;
itemId: string;
borrowDate: Date;
dueDate: Date;
returnDate?: Date;
lateFee?: number;
}
interface Reservation {
id: string;
memberId: string;
itemId: string;
reservationDate: Date;
expiryDate: Date;
status: "active" | "fulfilled" | "expired";
}
// ๐๏ธ Library system interface
interface LibrarySystem {
items: LibraryItem[];
members: LibraryMember[];
borrowingRecords: BorrowingRecord[];
reservations: Reservation[];
// Media management
addItem(item: LibraryItem): void;
removeItem(itemId: string): boolean;
searchItems(query: string): LibraryItem[];
// Member management
registerMember(member: LibraryMember): void;
findMember(memberId: string): LibraryMember | undefined;
// Borrowing operations
borrowItem(memberId: string, itemId: string): BorrowingRecord;
returnItem(recordId: string): void;
calculateLateFee(record: BorrowingRecord): number;
// Reservation operations
reserveItem(memberId: string, itemId: string): Reservation;
cancelReservation(reservationId: string): void;
}
// ๐จโ๐ผ Librarian interface
interface Librarian extends Member {
employeeId: string;
permissions: Permission[];
// Special operations
waiveLateFee(recordId: string): void;
extendDueDate(recordId: string, days: number): void;
generateReport(type: ReportType): Report;
}
interface Permission {
action: "add_item" | "remove_item" | "modify_member" | "waive_fees";
granted: boolean;
}
type ReportType = "overdue" | "popular_items" | "member_activity";
interface Report {
type: ReportType;
generatedAt: Date;
data: any;
}
// ๐๏ธ Implementation example
class PublicLibrary implements LibrarySystem {
items: LibraryItem[] = [];
members: LibraryMember[] = [];
borrowingRecords: BorrowingRecord[] = [];
reservations: Reservation[] = [];
private lateFeePerDay = 0.50;
addItem(item: LibraryItem): void {
this.items.push(item);
console.log(`๐ Added: ${item.title}`);
}
removeItem(itemId: string): boolean {
const index = this.items.findIndex(item => item.id === itemId);
if (index !== -1) {
this.items.splice(index, 1);
return true;
}
return false;
}
searchItems(query: string): LibraryItem[] {
const lowerQuery = query.toLowerCase();
return this.items.filter(item =>
item.title.toLowerCase().includes(lowerQuery) ||
(item.type === "book" && item.authors.some(author =>
author.toLowerCase().includes(lowerQuery)
))
);
}
registerMember(member: LibraryMember): void {
this.members.push(member);
console.log(`๐ค New member: ${member.name}`);
}
findMember(memberId: string): LibraryMember | undefined {
return this.members.find(member => member.id === memberId);
}
borrowItem(memberId: string, itemId: string): BorrowingRecord {
const member = this.findMember(memberId);
const item = this.items.find(i => i.id === itemId);
if (!member || !item || !item.available) {
throw new Error("Cannot borrow item");
}
const record: BorrowingRecord = {
id: `BR-${Date.now()}`,
memberId,
itemId,
borrowDate: new Date(),
dueDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) // 2 weeks
};
item.available = false;
this.borrowingRecords.push(record);
console.log(`โ
${member.name} borrowed "${item.title}"`);
return record;
}
returnItem(recordId: string): void {
const record = this.borrowingRecords.find(r => r.id === recordId);
if (!record) throw new Error("Record not found");
const item = this.items.find(i => i.id === record.itemId);
if (item) item.available = true;
record.returnDate = new Date();
record.lateFee = this.calculateLateFee(record);
console.log(`๐ Item returned${record.lateFee > 0 ? ` with $${record.lateFee} late fee` : ''}`);
}
calculateLateFee(record: BorrowingRecord): number {
if (!record.returnDate) return 0;
const daysLate = Math.max(0,
Math.floor((record.returnDate.getTime() - record.dueDate.getTime()) / (1000 * 60 * 60 * 24))
);
return daysLate * this.lateFeePerDay;
}
reserveItem(memberId: string, itemId: string): Reservation {
const reservation: Reservation = {
id: `RES-${Date.now()}`,
memberId,
itemId,
reservationDate: new Date(),
expiryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 1 week
status: "active"
};
this.reservations.push(reservation);
console.log(`๐
Item reserved`);
return reservation;
}
cancelReservation(reservationId: string): void {
const index = this.reservations.findIndex(r => r.id === reservationId);
if (index !== -1) {
this.reservations.splice(index, 1);
console.log(`โ Reservation cancelled`);
}
}
}
// ๐ฎ Usage example
const library = new PublicLibrary();
// Add a book
const typeScriptBook: Book = {
id: "BOOK-001",
type: "book",
title: "Learning TypeScript",
isbn: "978-1-098-11033-8",
authors: ["Josh Goldberg"],
publisher: "O'Reilly",
publishYear: 2022,
pages: 320,
genre: ["Programming", "Web Development"],
available: true,
location: "Shelf A-15"
};
library.addItem(typeScriptBook);
// Register a member
const student: Student = {
id: "MEM-001",
memberType: "student",
name: "Alice Johnson",
email: "[email protected]",
phone: "555-0123",
memberSince: new Date(),
isActive: true,
borrowingLimit: 5,
studentId: "STU-2024-001",
school: "Tech University"
};
library.registerMember(student);
// Borrow a book
const borrowing = library.borrowItem(student.id, typeScriptBook.id);
console.log(`Due date: ${borrowing.dueDate.toLocaleDateString()} ๐
`);
๐ Key Takeaways
Youโve mastered TypeScript interfaces! Hereโs what you can now do:
- โ Create interfaces to define object shapes ๐ช
- โ Use optional and readonly properties effectively ๐ฏ
- โ Extend interfaces to build complex types ๐๏ธ
- โ Implement interfaces in classes ๐ก๏ธ
- โ Design better APIs with clear contracts! ๐
Remember: Interfaces are the backbone of TypeScriptโs type system - use them to make your code more predictable and maintainable! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve unlocked the power of interfaces!
Hereโs what to do next:
- ๐ป Complete the library system exercise
- ๐๏ธ Refactor existing code to use interfaces
- ๐ Learn about generic interfaces
- ๐ Explore type aliases and union types!
Remember: Great software is built on solid contracts. Keep defining those interfaces! ๐
Happy coding! ๐๐โจ