Chapter 4: Objects, Classes, and Interfaces
TypeScript builds large codebases around object shapes, class features, and contracts defined by interfaces. This chapter explains how structural typing guides compatibility, how classes provide fields and accessors, how implements and extends guide reuse, and how abstract classes and mixin patterns help you model flexible hierarchies.
Structural typing
TypeScript uses structural typing; two values are compatible when their shapes match. This differs from nominal typing where names decide compatibility. When you create object literals, the compiler also performs excess property checks to catch likely mistakes such as misspelled properties.
Structural compatibility
If a value has at least the required members of a target type, it is assignable. Extra members usually do not block assignment when the value is not a fresh object literal.
type Point = { x: number; y: number };
const p1 = { x: 1, y: 2, label: "A" };
let pt: Point = p1; // OK; p1 has x and y
Excess property checks for fresh literals
When assigning an object literal directly, the compiler treats it as fresh. Unknown properties trigger a diagnostic which often reveals typos.
type Options = { timeout: number; verbose?: boolean };
const good: Options = { timeout: 5000 };
const bad: Options = { timeout: 5000, verbsoe: true }; // Error; excess property
If you intend a flexible bag of properties, add an index signature or widen with an intermediate variable.
type Bag = { [key: string]: string | number };
const cfg: Bag = { host: "localhost", port: 5432, … };
Readonly properties and exactness
readonly marks intent and prevents writes. Structural typing still applies; a type with readonly members is compatible with another that matches those members as readable fields.
type ReadonlyPoint = { readonly x: number; readonly y: number };
const rp: ReadonlyPoint = { x: 0, y: 0 };
// rp.x = 1; // Error; cannot assign to readonly
Classes, fields, accessors, and visibility
Classes in TypeScript compile to JavaScript classes. You can declare fields, use parameter properties, add get and set accessors, and control visibility with public, private, and protected. You can also mark fields readonly to communicate intent and prevent mutation.
Declaring fields
Fields can be declared with types and initializers. Parameter properties let you declare and initialize a field from a constructor parameter in one step.
class User {
readonly id: string;
constructor(public name: string, id: string) {
this.id = id;
}
}
const u = new User("Riley", "u-001");
Accessors with get and set
Accessors expose derived values or validate assignments. A setter can narrow input and guard invariants.
class Rectangle {
constructor(private _w: number, private _h: number) {}
get area(): number { return this._w * this._h; }
get width(): number { return this._w; }
set width(v: number) {
if (v <= 0) throw new Error("width must be positive");
this._w = v;
}
}
Managing visibility
public is the default visibility type. private hides members from all external code. protected exposes members to subclasses while keeping them hidden from other consumers.
class Base {
public a = 1;
protected b = 2;
private c = 3;
}
class Derived extends Base {
demo() {
this.a; // OK
this.b; // OK
// this.c; // Error; private
}
}
private for invariants and protected for subclass hooks; this keeps APIs small and intention clear.
implements and extends
implements checks that a class satisfies an interface. extends creates a subclass that inherits members and behavior. These features combine structural contracts with reuse.
Using implements for contracts
A class can implement one or more interfaces. The compiler verifies that required members exist with compatible types.
interface Serializer<T> { serialize(value: T): string; }
class JsonSerializer<T> implements Serializer<T> {
serialize(value: T): string { return JSON.stringify(value); }
}
Using extends for inheritance
Subclasses add or refine behavior. Use super to call the base constructor or methods.
class Animal {
constructor(public name: string) {}
speak(): string { return "..." }
}
class Dog extends Animal {
speak(): string { return "woof" }
}
Multiple contracts
A class may implement many interfaces, and it may extend exactly one base class. Combine both when you need reuse and guarantees.
interface Describable { describe(): string; }
class Service extends JsonSerializer<unknown> implements Describable {
describe(): string { return "Service using JSON" }
}
Abstract classes
Abstract classes define partial implementations with abstract members that subclasses must provide. Mixin patterns compose behavior by creating class factories that add members to a base class. Use abstract classes when you want inheritable behavior with enforced overrides; use mixins when you prefer composition with small reusable traits.
Defining and extending an abstract class
Mark a class abstract to prevent direct construction. Mark methods abstract to require overrides.
abstract class Formatter {
abstract format(value: unknown): string;
prefix(v: string): string { return "[[" + v + "]]"; }
}
class UpperFormatter extends Formatter {
format(value: unknown): string { return this.prefix(String(value).toUpperCase()); }
}
const f = new UpperFormatter();
// const bad = new Formatter(); // Error; abstract
A simple mixin factory
A mixin is a function that takes a constructor and returns a new constructor with added members. This composes features without deep inheritance.
type Ctor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Ctor>(Base: TBase) {
return class extends Base {
createdAt = new Date();
touch() { this.createdAt = new Date(); }
};
}
class Model { id = crypto.randomUUID(); }
class Note extends Timestamped(Model) {
text = "";
}
const n = new Note();
n.touch();
implements on the mixed class to advertise the shape publicly.
Choosing between abstract classes and mixins
Prefer an abstract base when subclasses share a core algorithm with required hooks; prefer mixins when you want orthogonal capabilities such as logging, events, timestamps, or caching that apply to unrelated classes.