Types and Interfaces in TypeScript

· 6 min read · Updated March 7, 2026 · beginner
types interfaces typescript beginner

Now that you have learned the basics of TypeScript, it is time to dive into two of the most important concepts in the language: types and interfaces. These building blocks allow you to define custom type definitions that make your code more readable, maintainable, and type-safe.

In this tutorial, you will learn how to create type aliases, understand the differences between types and interfaces, and master advanced patterns like extending and combining types.

Type Aliases

A type alias creates a new name for an existing type. They are useful for creating descriptive names for complex types.

Creating Type Aliases

Use the type keyword to create a type alias:

type ID = string;
type Point = { x: number; y: number };
type Callback = (result: string) => void;

Now you can use these aliases throughout your code:

type UserID = string;

function getUserById(id: UserID): UserID {
  return id;
}

const userId: UserID = "abc123";

Union Types with Type Aliases

Type aliases are powerful when combined with union types:

type Status = "pending" | "active" | "completed";

function setStatus(status: Status): void {
  console.log(`Setting status to: ${status}`);
}

setStatus("active");   // OK
setStatus("deleted");  // Error: Type '"deleted"' is not assignable to type 'Status'

Intersection Types

Combine multiple types using the & operator:

type Named = { name: string };
type Aged = { age: number };

type Person = Named & Aged;

const person: Person = {
  name: "Alice",
  age: 30
};

Interfaces

Interfaces are similar to type aliases but have some important differences. They are primarily used to define object shapes.

Defining Interfaces

Create an interface using the interface keyword:

interface User {
  id: string;
  name: string;
  email: string;
}

const user: User = {
  id: "1",
  name: "Alice",
  email: "alice@example.com"
};

Optional Properties

Add optional properties using the ? operator:

interface User {
  id: string;
  name: string;
  email?: string;  // Optional
  age?: number;   // Optional
}

const user1: User = {
  id: "1",
  name: "Alice"
};

const user2: User = {
  id: "2",
  name: "Bob",
  email: "bob@example.com",
  age: 25
};

Readonly Properties

Prevent modification with the readonly modifier:

interface Config {
  readonly apiKey: string;
  readonly baseUrl: string;
}

const config: Config = {
  apiKey: "secret123",
  baseUrl: "https://api.example.com"
};

config.apiKey = "newkey";  // Error: Cannot assign to 'apiKey' because it is a read-only property

Types vs Interfaces

Both types and interfaces can define object shapes, but they have different use cases.

Key Differences

FeatureType AliasInterface
Object shapesYesYes
Extend other typesIntersection (&)Extends keyword
Union typesYesNo
PrimitivesYesNo
Tuple typesYesNo
Declaration mergingNoYes

When to Use Type Aliases

Use type aliases when:

  • Working with primitives, unions, or tuples
  • Creating function types
  • Using intersection types
// Primitives
type StringOrNumber = string | number;

// Function types
type LogFunction = (message: string) => void;

// Tuples
type Point = [number, number];

When to Use Interfaces

Use interfaces when:

  • Defining object shapes that may be extended
  • Working with classes that implement contracts
  • You need declaration merging
interface Animal {
  name: string;
}

interface Animal {
  age: number;
}

// Animal now has both name and age
const animal: Animal = {
  name: "Dog",
  age: 3
};

Extending Interfaces

Interfaces can extend other interfaces to inherit their properties.

Basic Extension

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

interface Employee extends Person {
  employeeId: string;
  department: string;
}

const employee: Employee = {
  name: "Alice",
  age: 30,
  employeeId: "E001",
  department: "Engineering"
};

Multiple Extension

An interface can extend multiple interfaces:

interface Named {
  name: string;
}

interface Dated {
  createdAt: Date;
  updatedAt: Date;
}

interface Auditable extends Named, Dated {
  id: string;
}

const record: Auditable = {
  id: "1",
  name: "Document",
  createdAt: new Date(),
  updatedAt: new Date()
};

Extending Types

Type aliases can be extended using intersection types.

Type Extension

type Person = {
  name: string;
  age: number;
};

type Employee = Person & {
  employeeId: string;
  department: string;
};

const employee: Employee = {
  name: "Alice",
  age: 30,
  employeeId: "E001",
  department: "Engineering"
};

Interface Methods

Interfaces can define methods along with properties.

Method Syntax

interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
}

const calculator: Calculator = {
  add(a, b) {
    return a + b;
  },
  subtract(a, b) {
    return a - b;
  }
};

console.log(calculator.add(5, 3));      // 8
console.log(calculator.subtract(10, 4)); // 6

Optional Methods

Methods can also be optional:

interface Logger {
  info(message: string): void;
  warn(message: string): void;
  error(message: string): void;
  debug?(message: string): void;  // Optional
}

const logger: Logger = {
  info(msg) { console.log("INFO:", msg); },
  warn(msg) { console.log("WARN:", msg); },
  error(msg) { console.log("ERROR:", msg); }
  // debug is not implemented but allowed
};

Index Signatures

Define dynamic property names using index signatures.

Basic Index Signature

interface StringDictionary {
  [key: string]: string;
}

const dict: StringDictionary = {
  hello: "world",
  foo: "bar",
  key: "value"
};

Mixed Index Signature

Combine fixed and dynamic properties:

interface User {
  id: string;
  name: string;
  [key: string]: string | number;
}

const user: User = {
  id: "1",
  name: "Alice",
  email: "alice@example.com",
  age: 30,
  location: "London"
};

Generic Interfaces

Interfaces can be generic, making them flexible and reusable.

Basic Generic Interface

interface Container<T> {
  value: T;
  getValue(): T;
}

const stringContainer: Container<string> = {
  value: "hello",
  getValue() {
    return this.value;
  }
};

const numberContainer: Container<number> = {
  value: 42,
  getValue() {
    return this.value;
  }
};

Multiple Generic Parameters

interface Pair<K, V> {
  key: K;
  value: V;
}

const pair: Pair<string, number> = {
  key: "age",
  value: 30
};

Practical Example: Building a User System

Let us combine everything into a practical example:

// Base types
type UserStatus = "active" | "inactive" | "suspended";

// Base interface
interface BaseEntity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

// Extended interface
interface User extends BaseEntity {
  username: string;
  email: string;
  status: UserStatus;
  profile?: UserProfile;
}

// Nested interface
interface UserProfile {
  firstName: string;
  lastName: string;
  avatar?: string;
  preferences: UserPreferences;
}

interface UserPreferences {
  theme: "light" | "dark";
  notifications: boolean;
}

// Create a user
const user: User = {
  id: "1",
  username: "alice",
  email: "alice@example.com",
  status: "active",
  createdAt: new Date(),
  updatedAt: new Date(),
  profile: {
    firstName: "Alice",
    lastName: "Smith",
    preferences: {
      theme: "dark",
      notifications: true
    }
  }
};

// Function using our types
function updateUserStatus(user: User, status: UserStatus): User {
  return {
    ...user,
    status,
    updatedAt: new Date()
  };
}

const updatedUser = updateUserStatus(user, "inactive");
console.log(updatedUser.status);  // "inactive"

Where to Go From Here

You have learned the fundamentals of types and interfaces in TypeScript. These concepts are essential for building type-safe applications.

  • Generics: Learn how to create reusable, type-safe components
  • Utility Types: Explore built-in types like Partial, Required, Pick, and Omit
  • Advanced Types: Master conditional types, mapped types, and template literal types

The next tutorial in this series covers generics in TypeScript, which allows you to create components that work with any data type while maintaining type safety.

Conclusion

Type aliases and interfaces are powerful tools in TypeScript. Type aliases are ideal for working with primitives, unions, and function types, while interfaces excel at defining object shapes and supporting declaration merging. Understanding when to use each will help you write cleaner, more maintainable TypeScript code.

Practice creating your own types and interfaces, and experiment with extending and combining them. These skills form the foundation for building robust TypeScript applications.