Reading Interface Patterns
interface vs type, extends inheritance, readonly, optional properties, declaration merging — reading TypeScript interface patterns
TypeScript interface vocabulary
- interface vs type — equivalent for object shapes; interface supports declaration merging; type supports unions/mapped types
- extends — inherits all properties from the parent interface; supports multiple parents; chains transitively
- readonly — property can be set at creation but not reassigned; compile-time only, not enforced at runtime
- prop?: Type — optional: the property may be absent or undefined; required if no
? - Declaration merging — same-named interfaces are combined; used to augment third-party types (e.g., Express Request)
Question 0 of 5
When does the choice between interface and type matter in TypeScript?// These are equivalent:
interface User { id: number; name: string; }
type User = { id: number; name: string; };
// interface-only: declaration merging
interface Window { myPlugin: () => void; }
interface Window { theme: string; }
// Result: Window has both myPlugin and theme
// type-only: unions and intersections
type StringOrNumber = string | number;
type AdminUser = User & { role: "admin" };
- Declaration merging — declaring the same interface twice merges the properties; useful for augmenting third-party type definitions (e.g., extending
Express.Request) - type aliases — can define union types (
A | B), intersection types, tuple types, conditional types — interface cannot - extends — both interface and type aliases can extend; interface uses
extends, type uses& - Convention — many codebases use interface for public APIs (because they are extensible) and type for internal/complex types
Read this TypeScript interface. What does extends inherit?interface Entity {
id: number;
createdAt: Date;
updatedAt: Date;
}
interface User extends Entity {
name: string;
email: string;
}
interface AdminUser extends User {
permissions: string[];
canAccessAdmin: boolean;
}
const admin: AdminUser = {
id: 1,
createdAt: new Date(),
updatedAt: new Date(),
name: "Alice",
email: "alice@example.com",
permissions: ["read", "write"],
canAccessAdmin: true,
};
- extends — the child interface includes all properties of the parent; they are required unless individually made optional
- Multiple inheritance —
interface C extends A, Bis valid; TypeScript merges both parents - Conflict — if a child redefines a parent property with a different type, it must be a subtype of the parent's type
- vs. type intersection (&) —
type AdminUser = User & { permissions: string[] }is equivalent for most cases
What does the readonly modifier on an interface property mean?interface Config {
readonly apiUrl: string;
readonly maxRetries: number;
timeout: number;
}
const config: Config = {
apiUrl: "https://api.example.com",
maxRetries: 3,
timeout: 5000,
};
config.apiUrl = "https://other.com"; // Error: readonly
config.timeout = 10000; // OK
- readonly property — can be assigned in the initialiser (
const c: Config = { apiUrl: "..." }) but not after - Shallow — only the reference is readonly; if the property is an object, its nested properties can still be mutated
- readonly vs const —
constprevents variable reassignment;readonlyprevents property reassignment on an object - Readonly<T> — applies readonly to all properties of T at once (utility type)
How is an optional property name?: string different from a required property name: string?interface CreateUserInput {
name: string; // required
email: string; // required
bio?: string; // optional
avatarUrl?: string; // optional
}
// Valid:
const input1: CreateUserInput = { name: "Alice", email: "a@b.com" };
// Valid:
const input2: CreateUserInput = { name: "Bob", email: "b@c.com", bio: "Developer" };
// Error — missing required name:
const input3: CreateUserInput = { email: "c@d.com" };
- name?: string — type is
string | undefined; you must check before using:if (input.bio)orinput.bio?.length - Required property — omitting it in the object literal is a compile error
- exactOptionalPropertyTypes — with this tsconfig flag,
name?: stringmeans the property can be absent but NOT set toundefinedexplicitly - Required<T> — utility type that makes all optional properties required
What is declaration merging with interface and when is it useful?// third-party library types
interface Request {
method: string;
url: string;
}
// your augmentation in express.d.ts
interface Request {
user?: { id: number; role: string };
requestId: string;
}
// Now TypeScript knows Request has all four properties:
function handler(req: Request) {
console.log(req.requestId); // OK
console.log(req.user?.id); // OK
}
- Merging — TypeScript combines all declarations of the same interface name into one type; the result has all members from all declarations
- Module augmentation — the standard pattern:
declare module "express" { interface Request { user?: User; } }in a.d.tsfile - Why interface, not type — type aliases CANNOT be redeclared; this is the key reason to use interface for extensible APIs
- Conflict rule — function members with the same name are merged as overloads; non-function members must have compatible types