TypeScript interfaces & types

Sarah - Feb 21 - - Dev Community

What we'll cover

  • How TypeScript interfaces and types help structure your code
  • When to use interfaces vs types
  • Working with optional and readonly properties
  • Extending and combining interfaces
  • Real-world interface patterns

Interface basics

Interfaces in TypeScript define a contract for object shapes. Think of an interface like a blueprint - it tells TypeScript exactly what properties and methods an object should have. This helps catch errors when you accidentally miss a required property or use the wrong type.


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

let user: User = {
    name: "Alice",
    age: 30
}

// TypeScript error:
let incomplete: User = {
    name: "Bob"
    // Property 'age' is missing
}
Enter fullscreen mode Exit fullscreen mode

Type aliases

While interfaces work great for objects, sometimes we need more flexibility. Type aliases can do everything interfaces can do, plus they can create custom types like unions (combining multiple types with |) or even simple renames of existing types. This makes them perfect for creating reusable, complex types.

typescriptCopytype Point = {
    x: number;
    y: number;
}

type ID = string | number;  // Union type
Enter fullscreen mode Exit fullscreen mode

Try out some examples in TypeScript's playground with instant live preview

Optional and readonly properties

In real applications, not every property is always required or modifiable. TypeScript lets us mark properties as optional with ? or as read-only using readonly. This is particularly useful when working with APIs where some data might not always be available, or when you want to prevent accidental modifications.

interface Article {
    title: string;
    content: string;
    readonly publishDate: Date;  // Once set, can't be changed
    tags?: string[];            // Might not always have tags
    [rest of example]
Enter fullscreen mode Exit fullscreen mode

Extending interfaces

One of the most powerful features of interfaces is their ability to build on top of each other. Instead of copying and pasting common properties, you can extend an existing interface and add new properties. This follows the Don't Repeat Yourself(DRY) principle and makes your code more maintainable.

typescriptCopyinterface Animal {
    name: string;
}

interface Dog extends Animal {
    breed: string;
    bark(): void;
}
Enter fullscreen mode Exit fullscreen mode

Interface vs type: when to use each

A common question in TypeScript is whether to use an interface or a type alias. The key difference is that interfaces are extendable (can be modified after creation) while types are fixed once created. Understanding this helps you make the right choice for your use case.

// Interface can be extended later
interface Animal {
    name: string;
}

// Adding new properties to existing interface
interface Animal {
    age: number;
}

// Type cannot be extended after creation
type Plant = {
    name: string;
}

// Error: Duplicate identifier 'Plant'
type Plant = {
    age: number;
}
Enter fullscreen mode Exit fullscreen mode

Common gotchas

1. Interface merging

One surprising feature of interfaces is that they automatically merge when you define the same interface name multiple times. While this can be useful, it can also lead to unexpected behaviour if you're not aware of it.

// These merge automatically
interface Config {
    apiUrl: string;
}
interface Config {
    timeout: number;
}

// Results in:
// interface Config {
//     apiUrl: string;
//     timeout: number;
// }
Enter fullscreen mode Exit fullscreen mode

2. readonly vs const

The readonly modifier and const keyword might seem similar, but they work at different levels. readonly prevents property modification, while const prevents reassignment of the entire variable.

interface ReadonlyUser {
    readonly name: string;
}

const user: ReadonlyUser = { name: "Alice" };
// Error: Cannot assign to 'name' because it is a read-only property
user.name = "Bob";

// But the object itself can be reassigned
let mutableUser: ReadonlyUser = { name: "Alice" };
mutableUser = { name: "Bob" }; // This works!
Enter fullscreen mode Exit fullscreen mode

3. Optional chaining

When working with deeply nested optional properties in interfaces, TypeScript's optional chaining operator (?.) becomes invaluable for safely accessing properties that might not exist.

interface DeepObject {
    level1?: {
        level2?: {
            value?: string;
        };
    };
}

const obj: DeepObject = {};
// Safe access with optional chaining
const value = obj.level1?.level2?.value;
Enter fullscreen mode Exit fullscreen mode

Key takeaways

  • Interfaces define object shapes and act as contracts
  • Optional properties (?) make fields optional
  • readonly prevents property modification
  • Interfaces can be extended and merged
  • Type aliases offer additional flexibility for unions and intersections
  • Choose interface for object shapes that might need extension
  • Use type for unions, intersections, and mapped types

Further learning

Try TypeScript's playground with instant live preview and console

Look at TypeScript's documentation on interfaces

TypeScript cheatsheets

What to learn next?

Generics: creating reusable and type-safe components.

. . .