TypeScript Interfaces
A quick reference for defining object contracts, optional fields, index signatures, and interface inheritance.
TL;DR
- Define object contracts using the
interfacekeyword and typed members. - Mark non-mandatory fields optional using the
?modifier. - Compose multiple structural contracts together with the
extendskeyword.
Interface Syntax
interface declarationDefines the required shape and types of an object.
interface User {
id: number;
username: string;
}optional propertyPermits omission of a property using a question mark.
interface Profile {
displayName: string;
bio?: string;
}readonly modifierPrevents modifying property values after object creation.
interface Point {
readonly x: number;
readonly y: number;
}typed object literalAnnotates an object to enforce contract adherence.
const member: User = {
id: 101,
username: 'pat_dev'
};Methods and Signatures
method signatureSpecifies function names, parameters, and return shapes.
interface Calculator {
add(a: number, b: number): number;
clear(): void;
}function type interfaceDescribes callable function contracts using call signatures.
interface Formatter {
(input: string, uppercase?: boolean): string;
}
const format: Formatter = (s) => s.trim();construct signatureDescribes newable constructor signatures for factory patterns.
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}Extending Interfaces
extends keywordInherits properties from a base interface into a child.
interface Vehicle {
wheels: number;
}
interface Car extends Vehicle {
make: string;
}multiple inheritanceCombines properties from multiple interfaces separated by commas.
interface Named { name: string; }
interface Aged { age: number; }
interface Person extends Named, Aged {
id: string;
}property narrowingRefines an inherited property to a more specific subtype.
interface Event {
type: string;
}
interface ClickEvent extends Event {
type: 'click';
x: number;
}Index Signatures
string index signatureAllows arbitrary string keys mapped to a consistent type.
interface StringDictionary {
[key: string]: string;
}
const headers: StringDictionary = {
'Content-Type': 'application/json'
};numeric index signatureDescribes array-like structures accessed by numeric indexes.
interface StringArray {
[index: number]: string;
length: number;
}
const list: StringArray = ['a', 'b'];readonly indexLocks dictionary keys from runtime reassignment.
interface ReadonlyConfig {
readonly [key: string]: boolean;
}
const flags: ReadonlyConfig = { debug: true };Declaration Merging
interface mergingCombines two declarations with the same name into one.
interface Box {
height: number;
}
interface Box {
width: number;
}
const box: Box = { height: 10, width: 20 };global augmentationExtends global browser or runtime types cleanly.
interface Window {
analyticsTracker?: {
track(event: string): void;
};
}class interface mergeMerges an interface with a class to declare extra properties.
class Album {}
interface Album {
artist: string;
}
const a = new Album();
a.artist = 'Daft Punk';Tips
- Prefer
interfaceovertypefor defining public library API boundaries and contracts that external consumers might need to extend. - Combine
readonlyand optional property modifiers to create robust configuration interfaces that resist accidental runtime mutations.
Warnings
- Be cautious when reusing
interfaceidentifiers because TypeScript automatically applies declaration merging across files in identical scopes. - Remember that assigning fresh object literals enforces excess property checks that do not trigger when passing intermediate
constreferences.
In Practice
Demonstrates object shapes, optional fields, method contracts, and interface inheritance in a repository pattern.
- Declare a base entity interface with readonly identifiers and timestamp metadata.
- Extend the base entity to define specific user profile properties.
- Define an asynchronous repository interface declaring standard CRUD operations.
- Implement the repository contract in an in-memory class verifying type safety.
interface BaseEntity {
readonly id: string;
}
interface UserRecord extends BaseEntity {
email: string;
role: 'admin' | 'member';
}
interface UserRepo {
get(id: string): UserRecord | null;
save(u: UserRecord): void;
}
class MemoryRepo implements UserRepo {
private map = new Map<string, UserRecord>();
get(id: string) { return this.map.get(id) ?? null; }
save(u: UserRecord) { this.map.set(u.id, u); }
}FAQ
Use interface for object models, public API contracts, and classes where declaration merging or subclass extension is desired. Use type aliases for unions, intersections, primitive aliases, and mapped type operations where interfaces cannot be used.
Add an index signature like [key: string]: string | number to describe objects with unknown keys. Keep in mind that all explicitly defined properties in the interface must conform to the index signature's return type.
Yes, an interface can extend multiple interfaces simultaneously using a comma-separated list after extends. TypeScript merges all required and optional properties from every parent contract into the derived interface definition.
interface AdminUser extends User, Permissions {}Declaration merging happens when two interfaces share the exact same identifier in the same scope, merging their members into one contract. This is particularly valuable when augmenting existing types from third-party npm packages or standard library definitions.