Lesson 3.4: Interfaces
Duration: 60 minutes
Learning Objectives
By the end of this lesson, you will be able to:
- Define interfaces to describe object shapes
- Use interfaces for function parameters and return types
- Extend interfaces to build on existing definitions
- Implement interfaces in classes (preview)
- Understand when to use interfaces vs inline types
What is an Interface?
An interface defines the structure of an object - what properties it must have and what types those properties should be.
interface User {
name: string;
age: number;
email: string;
}
const alice: User = {
name: 'Alice',
age: 28,
email: 'alice@example.com',
};
If you try to create a User without all required properties, TypeScript will complain:
const bob: User = {
name: 'Bob',
age: 30,
// Error! Property 'email' is missing
};
Why Use Interfaces?
1. Catch Errors Early
interface Product {
id: number;
name: string;
price: number;
}
const laptop: Product = {
id: 1,
name: 'MacBook Pro',
proce: 2499, // Error! 'proce' does not exist. Did you mean 'price'?
};
2. Better Autocomplete
When you type laptop., your editor will suggest id, name, and price - nothing else.
3. Self-Documenting Code
Interfaces serve as documentation. Anyone reading your code can see exactly what a User or Product should contain.
4. Refactoring Safety
If you add a required property to an interface, TypeScript will show errors everywhere that property is missing.
Defining Interfaces
Basic Syntax
interface InterfaceName {
propertyName: propertyType;
anotherProperty: anotherType;
}
Example: Article Interface
interface Article {
title: string;
content: string;
author: string;
publishedAt: Date;
views: number;
}
const article: Article = {
title: 'Getting Started with TypeScript',
content: 'TypeScript is a typed superset of JavaScript...',
author: 'Alice',
publishedAt: new Date('2024-01-15'),
views: 1250,
};
Interfaces with Functions
Function Parameters
Interfaces make function signatures clear:
interface LoginCredentials {
username: string;
password: string;
}
function login(credentials: LoginCredentials): boolean {
console.log(`Logging in ${credentials.username}...`);
// Authentication logic here
return true;
}
login({ username: 'alice', password: 'secret123' });
login({ username: 'bob' }); // Error! Property 'password' is missing
Function Return Types
interface ApiResponse {
success: boolean;
data: string[];
timestamp: Date;
}
function fetchData(): ApiResponse {
return {
success: true,
data: ['item1', 'item2'],
timestamp: new Date(),
};
}
const response = fetchData();
console.log(response.data); // TypeScript knows this is string[]
Nested Interfaces
Interfaces can contain other interfaces or nested object types:
interface Address {
street: string;
city: string;
country: string;
zipCode: string;
}
interface Company {
name: string;
address: Address;
employees: number;
}
const techCorp: Company = {
name: 'TechCorp',
address: {
street: '123 Main St',
city: 'San Francisco',
country: 'USA',
zipCode: '94105',
},
employees: 500,
};
// Access nested properties
console.log(techCorp.address.city); // "San Francisco"
Arrays in Interfaces
interface ShoppingCart {
items: CartItem[];
total: number;
}
interface CartItem {
productId: number;
name: string;
quantity: number;
price: number;
}
const cart: ShoppingCart = {
items: [
{ productId: 1, name: 'Keyboard', quantity: 1, price: 99.99 },
{ productId: 2, name: 'Mouse', quantity: 2, price: 49.99 },
],
total: 199.97,
};
Extending Interfaces
Interfaces can extend other interfaces, inheriting their properties:
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: string;
department: string;
}
const employee: Employee = {
name: 'Alice',
age: 28,
employeeId: 'EMP001',
department: 'Engineering',
};
You can extend multiple interfaces:
interface Timestamps {
createdAt: Date;
updatedAt: Date;
}
interface SoftDelete {
deletedAt: Date | null;
}
interface BlogPost extends Timestamps, SoftDelete {
title: string;
content: string;
author: string;
}
const post: BlogPost = {
title: 'My First Post',
content: 'Hello, world!',
author: 'Alice',
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
};
Interfaces for Function Types
Interfaces can describe function signatures:
interface MathOperation {
(a: number, b: number): number;
}
const add: MathOperation = (a, b) => a + b;
const multiply: MathOperation = (a, b) => a * b;
console.log(add(5, 3)); // 8
console.log(multiply(5, 3)); // 15
Index Signatures
When you do not know all property names in advance, use an index signature:
interface StringDictionary {
[key: string]: string;
}
const translations: StringDictionary = {
hello: 'hola',
goodbye: 'adios',
thanks: 'gracias',
};
translations.welcome = 'bienvenido'; // OK
translations.count = 42; // Error! Type 'number' is not assignable to type 'string'
Combine index signatures with known properties:
interface Config {
name: string;
version: string;
[key: string]: string; // Any additional string properties
}
const config: Config = {
name: 'MyApp',
version: '1.0.0',
author: 'Alice',
license: 'MIT',
};
Interface vs Inline Type
You can define object types inline without an interface:
// Inline type
function printUser(user: { name: string; age: number }) {
console.log(`${user.name} is ${user.age} years old`);
}
// Using interface
interface User {
name: string;
age: number;
}
function printUser(user: User) {
console.log(`${user.name} is ${user.age} years old`);
}
When to Use Interfaces
- When the type will be reused in multiple places
- When you want to extend or merge the type later
- When documenting public APIs
- When the type represents a concept in your domain (User, Product, Order)
When Inline Types Are Fine
- One-time use parameter types
- Simple return types
- Quick prototyping
Declaration Merging
A unique feature of interfaces is that you can declare the same interface multiple times, and TypeScript merges them:
interface User {
name: string;
}
interface User {
age: number;
}
// User now has both properties
const user: User = {
name: 'Alice',
age: 28,
};
This is useful when extending third-party types, but use sparingly in your own code.
Practical Examples
Example 1: E-commerce Product System
interface Product {
id: string;
name: string;
price: number;
description: string;
category: string;
inStock: boolean;
}
interface Review {
userId: string;
rating: number;
comment: string;
date: Date;
}
interface ProductWithReviews extends Product {
reviews: Review[];
averageRating: number;
}
function displayProduct(product: ProductWithReviews) {
console.log(`${product.name} - $${product.price}`);
console.log(`Rating: ${product.averageRating}/5 (${product.reviews.length} reviews)`);
}
Example 2: Configuration System
interface DatabaseConfig {
host: string;
port: number;
database: string;
username: string;
password: string;
}
interface ServerConfig {
port: number;
hostname: string;
ssl: boolean;
}
interface AppConfig {
database: DatabaseConfig;
server: ServerConfig;
debug: boolean;
}
function initializeApp(config: AppConfig) {
console.log(`Connecting to ${config.database.host}:${config.database.port}`);
console.log(`Starting server on ${config.server.hostname}:${config.server.port}`);
}
Example 3: Event Handler System
interface EventHandler {
(event: string, data: unknown): void;
}
interface EventEmitter {
on: (event: string, handler: EventHandler) => void;
emit: (event: string, data: unknown) => void;
off: (event: string, handler: EventHandler) => void;
}
// Implementation would follow this contract
Exercises
Exercise 1: Define a Book Interface
Create an interface for a book with title, author, ISBN, pages, and published year:
Solution
interface Book {
title: string;
author: string;
isbn: string;
pages: number;
publishedYear: number;
}
const book: Book = {
title: 'TypeScript in Action',
author: 'John Doe',
isbn: '978-1234567890',
pages: 350,
publishedYear: 2024,
};
console.log(`${book.title} by ${book.author} (${book.publishedYear})`);
Exercise 2: Nested Interfaces for an Order
Create interfaces for an e-commerce order with customer info, items, and shipping address:
Solution
interface Customer {
id: string;
name: string;
email: string;
}
interface OrderItem {
productId: string;
name: string;
quantity: number;
unitPrice: number;
}
interface ShippingAddress {
street: string;
city: string;
state: string;
zipCode: string;
country: string;
}
interface Order {
orderId: string;
customer: Customer;
items: OrderItem[];
shippingAddress: ShippingAddress;
total: number;
status: string;
}
const order: Order = {
orderId: 'ORD-001',
customer: {
id: 'CUST-123',
name: 'Alice Smith',
email: 'alice@example.com',
},
items: [
{ productId: 'PROD-1', name: 'Laptop', quantity: 1, unitPrice: 999 },
{ productId: 'PROD-2', name: 'Mouse', quantity: 2, unitPrice: 29 },
],
shippingAddress: {
street: '123 Main St',
city: 'Seattle',
state: 'WA',
zipCode: '98101',
country: 'USA',
},
total: 1057,
status: 'pending',
};
Exercise 3: Extend an Interface
Create a base Vehicle interface and extend it to create Car and Motorcycle interfaces:
Solution
interface Vehicle {
make: string;
model: string;
year: number;
color: string;
}
interface Car extends Vehicle {
doors: number;
trunkSize: number;
fuelType: string;
}
interface Motorcycle extends Vehicle {
engineSize: number;
hasStorage: boolean;
}
const myCar: Car = {
make: 'Toyota',
model: 'Camry',
year: 2023,
color: 'Silver',
doors: 4,
trunkSize: 15,
fuelType: 'Hybrid',
};
const myBike: Motorcycle = {
make: 'Honda',
model: 'CBR600',
year: 2022,
color: 'Red',
engineSize: 600,
hasStorage: false,
};
Exercise 4: Function Interface
Create an interface for a comparison function and use it:
Solution
interface Comparator {
(a: number, b: number): number;
}
const ascending: Comparator = (a, b) => a - b;
const descending: Comparator = (a, b) => b - a;
const numbers = [5, 2, 8, 1, 9];
console.log([...numbers].sort(ascending)); // [1, 2, 5, 8, 9]
console.log([...numbers].sort(descending)); // [9, 8, 5, 2, 1]
Key Takeaways
- Interfaces define object shapes: Specify required properties and their types
- Use interfaces for reusable types: Especially for domain concepts like User, Product, Order
- Interfaces can be nested: Objects within objects are common and clean
- Extend interfaces: Build on existing definitions with
extends - Index signatures: Handle dynamic property names with
[key: string]: type - Declaration merging: Same-named interfaces combine (unique to interfaces)
- Choose wisely: Use interfaces for reusable, extendable types; inline types for one-offs
Resources
| Resource | Type | Description |
|---|---|---|
| TypeScript Handbook: Object Types | Documentation | Official guide to interfaces |
| TypeScript Handbook: Interfaces | Documentation | In-depth interface reference |
| TypeScript Playground | Tool | Experiment with interfaces online |
Next Lesson
You have learned how to describe object structures with interfaces. Next, we will explore type aliases - another way to create custom types with some unique capabilities.