Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/typescript-exercises/typescript-exercises/llms.txt

Use this file to discover all available pages before exploring further.

What are Interfaces?

Interfaces in TypeScript define the structure of objects, describing what properties and methods an object should have. They serve as contracts that enforce type safety across your codebase.

Basic Interface Definition

From Exercise 1, here’s a simple interface definition:
interface User {
    name: string;
    age: number;
    occupation: string;
}

const users: User[] = [
    {
        name: 'Max Mustermann',
        age: 25,
        occupation: 'Chimney sweep'
    },
    {
        name: 'Kate Müller',
        age: 23,
        occupation: 'Astronaut'
    }
];

function logPerson(user: User) {
    console.log(` - ${user.name}, ${user.age}`);
}
Interfaces provide compile-time type checking without adding any runtime overhead. They completely disappear during compilation to JavaScript.

Interface Extension

Interfaces can extend other interfaces to create more specific types:
interface Person {
    name: string;
    age: number;
}

interface User extends Person {
    occupation: string;
}

interface Admin extends Person {
    role: string;
}

Multiple Extensions

An interface can extend multiple interfaces:
interface Nameable {
    name: string;
}

interface Ageable {
    age: number;
}

interface Person extends Nameable, Ageable {
    type: string;
}

Interfaces vs Types

interface User {
    name: string;
    age: number;
}

// Can be extended
interface Employee extends User {
    employeeId: string;
}

// Can be reopened (declaration merging)
interface User {
    email?: string;
}
When to use Interfaces:
  • Defining object shapes that might be extended
  • Working with classes
  • When you need declaration merging
  • Public APIs where consumers might want to extend types
When to use Types:
  • Union types: type Status = 'pending' | 'active' | 'inactive'
  • Complex type compositions
  • Mapped types and conditional types
  • Primitive aliases

Practical Patterns

Optional Properties

interface User {
    name: string;
    age: number;
    occupation?: string;  // Optional property
}

const user: User = {
    name: 'John',
    age: 30
    // occupation is optional
};

Readonly Properties

interface User {
    readonly id: string;
    name: string;
    age: number;
}

const user: User = {
    id: 'user-123',
    name: 'John',
    age: 30
};

// user.id = 'new-id'; // Error: Cannot assign to 'id' because it is a read-only property

Index Signatures

When you don’t know all property names ahead of time:
interface UserPreferences {
    theme: 'light' | 'dark';
    [key: string]: any;  // Allows additional properties
}

const prefs: UserPreferences = {
    theme: 'dark',
    fontSize: 14,
    notifications: true
};

Function Interfaces

Interfaces can describe function types:
interface FilterFunction {
    (person: Person, criteria: Partial<Person>): boolean;
}

const matchesCriteria: FilterFunction = (person, criteria) => {
    return Object.keys(criteria).every(
        key => person[key] === criteria[key]
    );
};

Declaration Merging

One unique feature of interfaces is that multiple declarations with the same name automatically merge:
interface User {
    name: string;
    age: number;
}

interface User {
    email: string;
}

// Merged interface has all properties
const user: User = {
    name: 'John',
    age: 30,
    email: 'john@example.com'
};
While declaration merging is powerful, use it carefully. It can make code harder to understand if overused. It’s most useful when augmenting third-party types.

Interface with Literal Types

From Exercise 4, interfaces can use literal types for discriminated unions:
interface User {
    type: 'user';  // Literal type
    name: string;
    age: number;
    occupation: string;
}

interface Admin {
    type: 'admin';  // Literal type
    name: string;
    age: number;
    role: string;
}

type Person = User | Admin;

// TypeScript can discriminate based on the 'type' field
function getInfo(person: Person) {
    if (person.type === 'user') {
        return person.occupation;  // TypeScript knows this is User
    } else {
        return person.role;  // TypeScript knows this is Admin
    }
}

Best Practices

Instead of IUser or UserInterface, just use User. TypeScript conventions have moved away from prefixes like I.
// Good
interface User { ... }

// Avoid
interface IUser { ... }
interface UserInterface { ... }
Each interface should represent a single, well-defined concept:
// Good - Focused interfaces
interface User {
    name: string;
    email: string;
}

interface UserPermissions {
    canEdit: boolean;
    canDelete: boolean;
}

// Less ideal - Mixed concerns
interface User {
    name: string;
    email: string;
    canEdit: boolean;
    canDelete: boolean;
}
Mark properties as readonly when they shouldn’t change after initialization:
interface User {
    readonly id: string;
    name: string;
    age: number;
}

Type Guards

Learn how to narrow union types with type predicates

Utility Types

Transform interfaces with Partial, Pick, Omit, and more

Generics

Make interfaces reusable with generic type parameters

Exercise 1

Practice defining and using basic interfaces

Further Reading