Interfaces & Types: Modeling Custom Data
In real-world applications, you rarely work with just strings or numbers. You work with complex objects: Users, Products, API Responses, and more. TypeScript provides two powerful ways to define the "shape" of these objects: Interfaces and Type Aliases.
1. Interfaces: Defining Object Contracts
An interface is a powerful way to define the structure of an object. It acts as a contract that a variable or class must fulfill.
Basic Interface
interface User {
id: number;
username: string;
}
const user: User = { id: 1, username: "dev_pro" };
Advanced Interface Features
- Optional Properties (
?): Properties that don't have to be present. - Readonly Properties (
readonly): Properties that can only be set when the object is first created. - Function Types: Interfaces can also describe how a function should look.
interface SmartDevice {
readonly serialNumber: string;
model: string;
batteryLife?: number; // Optional
turnOn: (mode: string) => boolean; // Function signature
}
2. Type Aliases: The Flexible Alternative
A type alias allows you to create a new name for any type, including primitives, unions, and intersections.
Union Types (|)
Used when a value can be one of several types.
type ID = string | number;
let userId: ID = 101;
userId = "uuid-abcd";
Intersection Types (&)
Used to combine multiple types into one.
interface HasName { name: string; }
interface HasAge { age: number; }
type Person = HasName & HasAge;
const worker: Person = { name: "John", age: 30 };
3. Extending Types
Both Interfaces and Type Aliases can be extended, but the syntax differs.
Extending an Interface (Inheritance)
interface Animal { name: string; }
interface Dog extends Animal { breed: string; }
Extending via Intersections
type Animal = { name: string; };
type Dog = Animal & { breed: string; };
4. Key Differences: Interface vs. Type
While they are very similar, there are important technical differences:
- Declaration Merging: You can define the same
interfacetwice, and TypeScript will merge them into one. You cannot do this withtype.interface Window { title: string; } interface Window { width: number; } // Result: Window has both title and width. - Primitives/Unions: A
typecan alias a primitive (type Name = string) or a union. Aninterfacecan only describe objects or functions. - Extensibility: Interfaces are generally better for public APIs and libraries because they are designed for inheritance and merging.
5. Index Signatures
Sometimes you don't know the exact property names in advance (e.g., data from a database). You can use an Index Signature to define the types of keys and values.
interface StringDictionary {
[key: string]: string; // All keys must be strings, all values must be strings
}
const translations: StringDictionary = {
"hello": "hola",
"world": "mundo"
};
6. Summary
- Use
interfacefor object shapes, especially if you need to useextendsor Declaration Merging. - Use
typefor complex unions, intersections, or when aliasing primitives. - Always favor Consistency within your project.