From Zero to AI

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

  1. Interfaces define object shapes: Specify required properties and their types
  2. Use interfaces for reusable types: Especially for domain concepts like User, Product, Order
  3. Interfaces can be nested: Objects within objects are common and clean
  4. Extend interfaces: Build on existing definitions with extends
  5. Index signatures: Handle dynamic property names with [key: string]: type
  6. Declaration merging: Same-named interfaces combine (unique to interfaces)
  7. 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.

Continue to Lesson 3.5: Type Aliases