Classes, Deep
Two kinds of private
Section titled “Two kinds of private”TypeScript has two ways to make a member private, and they are not the same.
class Account { private balance = 0; // compile-time only — erased #pin = "1234"; // truly private — enforced at runtime
check() { return this.balance + Number(this.#pin); }}
const a = new Account();// a.balance; // ❌ compile error, but...// (a as any).balance; // ✅ reachable at runtime — private was erased// a.#pin; // ❌ SyntaxError even at runtime — hard privateprivate(andprotected) are compile-time access checks. They are erased on emit, so a determined caller can reach the field at runtime viaanyor bracket access. They document intent and catch honest mistakes.#namefields are ECMAScript private fields — genuinely inaccessible outside the class, enforced by the JavaScript engine itself. Reach for#when privacy must hold at runtime.
protected sits between: accessible in the class and its subclasses, but not from outside.
readonly fields
Section titled “readonly fields”readonly allows assignment only in the declaration or constructor, then locks the field. Like private, it is a compile-time guarantee — erased at runtime.
class Point { readonly x: number; constructor(x: number) { this.x = x; // ✅ allowed in the constructor } move() { // this.x = 5; // ❌ Cannot assign to 'x' because it is read-only. }}Parameter properties: the constructor shorthand
Section titled “Parameter properties: the constructor shorthand”Declaring a field and assigning it from a constructor parameter is so common that TypeScript has a shorthand: put an access modifier on the constructor parameter and it becomes a field automatically.
class User { constructor( public readonly id: string, private name: string, ) {} // No separate field declarations, no `this.id = id` — the modifiers do it.}This is equivalent to declaring id and name as fields and assigning them in the body. It is purely a convenience, but a widely used one.
static members
Section titled “static members”static members belong to the class itself, not to instances. They exist at runtime as properties on the constructor function.
class Circle { static readonly PI = 3.14159; static area(r: number) { return Circle.PI * r * r; }}
Circle.area(2); // called on the class, not an instanceabstract classes
Section titled “abstract classes”An abstract class cannot be instantiated directly; it exists to be extended. abstract methods declare a signature that subclasses must implement. The abstract markers are compile-time only.
abstract class Shape { abstract area(): number; // no body — subclasses must provide one describe() { return `area is ${this.area()}`; } // concrete method}
// new Shape(); // ❌ Cannot create an instance of an abstract class.
class Square extends Shape { constructor(private side: number) { super(); } area() { return this.side * this.side; } // required}implements is a check, not inheritance
Section titled “implements is a check, not inheritance”A class can implements an interface. This does not copy anything in — it is a compile-time assertion that the class structurally satisfies the interface. Because TypeScript is structural, a class can match an interface without implements; the clause just makes the intent explicit and turns a mismatch into an error at the class definition rather than at the use site.
interface Named { name: string; }
class Product implements Named { name = "widget"; // if this were missing, the error points HERE}