Classes: Access Modifiers, Abstract Classes & Implements

10 questions found

What do the public, private, and protected access modifiers control on a class member?

Beginner
public (the default) makes a member accessible from anywhere; private restricts access to only within the declaring class itself; protected allows access within the declaring class AND its subclasses, but not from outside code.
class Account {
  public balance: number = 0;
  private pin: string = '1234';
  protected accountType: string = 'checking';
}
Real-world example Hiding an internal PIN or password field so only the class's own methods can read it.

Common follow-ups: Are these modifiers enforced at runtime, or only during compilation?

Never Unknown & Void Types

How does the '#' private field syntax differ from the 'private' keyword?

Beginner
The 'private' keyword is a TypeScript-only, compile-time restriction — it's erased at compile time and the field is still a regular accessible property in the emitted JavaScript. The '#' syntax is real, native JavaScript privacy enforced at runtime, invisible and inaccessible even via bracket notation or reflection.
class Account {
  private tsPrivate = 1;   // compile-time only, visible in emitted JS
  #truePrivate = 2;        // enforced at runtime, invisible outside the class
}
Real-world example Choosing '#' private fields when you need guaranteed runtime encapsulation, such as protecting sensitive internal state from any external access.

Common follow-ups: Does '#' private syntax work in TypeScript targets older than ES2022?

Readonly const Assertions & Immutability

How do you declare and use an abstract class?

Beginner
Mark a class with the abstract keyword to prevent it from being instantiated directly with 'new' — it exists only to be extended, typically providing shared implementation plus one or more abstract methods that subclasses MUST implement.
abstract class Shape {
  abstract area(): number;
  describe(): string { return `Area: ${this.area()}`; }
}
class Circle extends Shape {
  constructor(private radius: number) { super(); }
  area() { return Math.PI * this.radius ** 2; }
}
// new Shape(); // Error: cannot instantiate an abstract class
Real-world example Defining a base PaymentMethod class where each subclass (CreditCard, PayPal) must implement its own charge() logic.

Common follow-ups: Can an abstract class have a constructor with implementation, even though it can't be instantiated directly?

Structural Typing & Duck Typing

What does the 'implements' keyword do, and how does it differ from 'extends'?

Intermediate
implements requires a class to satisfy an interface's shape — providing all its declared members — without inheriting any implementation or behavior. extends inherits actual behavior and state from a parent class (or interface, for interface-to-interface extension), and a class can only extend one class but implement multiple interfaces.
interface Flyable { fly(): void; }
interface Swimmable { swim(): void; }
class Duck implements Flyable, Swimmable {
  fly() { console.log('flying'); }
  swim() { console.log('swimming'); }
}
Real-world example Requiring a class to conform to a Serializable or Comparable-style contract without forcing a specific inheritance hierarchy.

Common follow-ups: Does 'implements' add any runtime type checking, or is it purely a compile-time contract?

Structural Typing & Duck Typing

What is parameter property shorthand in a constructor, and what does it expand to?

Intermediate
Prefixing a constructor parameter with an access modifier (public, private, protected, or readonly) automatically declares a class property of that name AND assigns the parameter's value to it, removing the need to write both a field declaration and an explicit `this.x = x` assignment.
class Point {
  constructor(private x: number, private y: number) {}
  // equivalent to declaring 'private x: number;' and 'this.x = x;' manually
}
Real-world example Reducing constructor boilerplate in small value classes like Point, Money, or Coordinates.

Common follow-ups: Can you mix parameter properties with regular constructor parameters in the same constructor?

Primitive Types Arrays & Tuples

How do static properties and methods work on a TypeScript class?

Intermediate
static members belong to the class itself rather than to instances, accessed as ClassName.member. TypeScript also supports static blocks for complex static initialization logic, and access modifiers can apply to static members too (e.g. private static).
class IdGenerator {
  private static nextId = 1;
  static generate(): number {
    return IdGenerator.nextId++;
  }
}
IdGenerator.generate(); // 1
Real-world example A shared, class-wide ID counter or a static factory method like User.fromJSON().

Common follow-ups: Can a subclass access a private static member of its parent class?

Modules

How does TypeScript enforce that an abstract method must be implemented, and what error occurs if it isn't?

Advanced
When a concrete (non-abstract) class extends an abstract class without implementing all its abstract members, the compiler raises an error like 'non-abstract class does not implement inherited abstract member', catching the omission at compile time before the code ever runs.
abstract class Shape {
  abstract area(): number;
}
class Broken extends Shape {}
// Error: Non-abstract class 'Broken' does not implement inherited abstract member 'area' from class 'Shape'.
Real-world example Guaranteeing every new payment provider or shape subtype implements its required core method, caught at compile time in CI.

Common follow-ups: Can an abstract class itself have some concrete (non-abstract) methods alongside abstract ones?

Discriminated Unions & Exhaustiveness Checking

How do access modifiers interact with structural typing when checking class compatibility?

Advanced
Structural typing normally only cares about shape, but private and protected members break this: two classes with identically-named private/protected members are still considered incompatible UNLESS one is literally derived from the other, because TypeScript tracks the declaring class as part of each private/protected member's identity.
class A { private secret = 1; }
class B { private secret = 1; }
let a: A = new B(); // Error: Property 'secret' is private in type 'B' but not related to A's 'secret'
Real-world example Understanding a confusing compiler error where two seemingly identical classes aren't assignable to each other.

Common follow-ups: Does this same restriction apply to 'public' members?

Structural Typing & Duck Typing

How does the 'protected constructor' pattern enforce a specific object-creation approach, like the Factory pattern?

Advanced
Making a class's constructor protected prevents external code from calling 'new' on it directly, while still allowing subclasses (or static factory methods within the class itself) to construct instances — commonly used to force construction through a validated static factory method.
class User {
  protected constructor(public readonly email: string) {}
  static create(email: string): User {
    if (!email.includes('@')) throw new Error('Invalid email');
    return new User(email);
  }
}
// new User('x'); // Error: constructor is protected
User.create('sam@example.com'); // OK
Real-world example Guaranteeing every User instance is validated at creation time by forcing all construction through User.create().

Common follow-ups: How does this compare to using a private constructor instead of protected?

Generics

What is the difference between an abstract class and an interface when both could technically express the same contract, and how do you decide which to use?

Advanced
An abstract class can provide shared implementation, state, and constructor logic that subclasses inherit for free, and supports access modifiers — but a class can only extend one. An interface is purely a shape contract with zero implementation, and a class can implement many. Choose an abstract class when subclasses genuinely share behavior/state; choose an interface when you only need to enforce a shape across otherwise unrelated classes.
// Shared behavior -> abstract class
abstract class Repository<T> {
  protected items: T[] = [];
  abstract validate(item: T): boolean;
  add(item: T) { if (this.validate(item)) this.items.push(item); }
}

// Pure contract -> interface
interface Identifiable { id: string; }
Real-world example Choosing an abstract Repository base class with shared CRUD logic, versus a plain Identifiable interface used across many unrelated types.

Common follow-ups: Can TypeScript interfaces declare and enforce a constructor signature the way abstract classes can?

Structural Typing & Duck Typing