Types and Interfaces in TypeScript
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
| Feature | Type Alias | Interface |
|---|---|---|
| Object shapes | Yes | Yes |
| Extend other types | Intersection (&) | Extends keyword |
| Union types | Yes | No |
| Primitives | Yes | No |
| Tuple types | Yes | No |
| Declaration merging | No | Yes |
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.