# Classes and Access Modifiers

> Source: https://learn-typescript.org/classes-and-modifiers/
> Part of Learn TypeScript, free to read.

JavaScript classes work in TypeScript unchanged. What TypeScript adds is access control the compiler enforces, and the ability to declare that a class satisfies an interface.

```typescript
class Account {
  readonly id: string;
  private balanceCents: number;
  protected currency: string;

  constructor(id: string, openingCents: number, currency = "USD") {
    this.id = id;
    this.balanceCents = openingCents;
    this.currency = currency;
  }

  deposit(cents: number): void {
    if (cents <= 0) throw new RangeError("deposit must be positive");
    this.balanceCents += cents;
  }

  get balance(): number {
    return this.balanceCents / 100;
  }
}
```

```typescript
const a = new Account("acc_1", 10_000);
a.deposit(500);
a.balance;             // 105
a.balanceCents;        // error: 'balanceCents' is private
a.id = "acc_2";        // error: 'id' is readonly
```

## The modifiers

| Modifier | Visible from |
|---|---|
| `public` (default) | anywhere |
| `protected` | the class and its subclasses |
| `private` | only inside the class |
| `readonly` | anywhere, but cannot be reassigned after construction |
| `static` | on the class itself, not instances |

:::warn `private` is compile-time only
It disappears when types are stripped, so at runtime the field is an ordinary property that anything can reach. For genuine runtime privacy use JavaScript's `#` fields:

```typescript
class Account {
  #balanceCents = 0;      // actually inaccessible outside the class
}
```
`private` is for catching mistakes in your own code. `#` is for enforcement.
:::

## Parameter properties

The constructor above is mostly boilerplate. TypeScript can declare and assign in one step:

```typescript
class Account {
  constructor(
    public readonly id: string,
    private balanceCents: number,
    protected currency: string = "USD",
  ) {}
}
```

Identical behaviour, far less repetition. A modifier on a constructor parameter creates the property and assigns it.

Two caveats: this is TypeScript-only syntax, so it is **not erasable** — it will not work under a runtime type-stripper or with `erasableSyntaxOnly`. And it is the mechanism most dependency-injection frameworks rely on, which is why NestJS controllers look the way they do.

## implements

`implements` states that a class satisfies an interface, and the compiler checks it:

```typescript
interface Repository<T> {
  findById(id: string): Promise<T | null>;
  save(item: T): Promise<void>;
}

class UserRepo implements Repository<User> {
  async findById(id: string): Promise<User | null> { /* … */ }
  async save(user: User): Promise<void> { /* … */ }
  // omit one and you get an error naming exactly what is missing
}
```

`implements` is a **check**, not inheritance — it adds nothing at runtime and the class must still write every member itself. Because TypeScript is structurally typed, a class with the right shape satisfies the interface whether or not it says `implements`; the keyword just moves the error to the class rather than to its first use.

## Abstract classes

A base that cannot be instantiated and can require subclasses to fill in the gaps:

```typescript
abstract class Shape {
  abstract area(): number;                 // subclasses must implement

  describe(): string {                     // shared implementation
    return `${this.constructor.name} with area ${this.area().toFixed(2)}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) { super(); }
  area(): number { return Math.PI * this.radius ** 2; }
}

new Shape();          // error: cannot create an instance of an abstract class
new Circle(2).describe();   // "Circle with area 12.57"
```

## When not to use a class

Classes are a tool, not a default. Reach for one when you have **state and behaviour that belong together**, and something that genuinely benefits from instances.

A class with no state, or one that is only ever instantiated once, is a namespace with extra steps:

```typescript
// no state — a class adds nothing
class MathUtils {
  static add(a: number, b: number) { return a + b; }
}

// just export the functions
export function add(a: number, b: number) { return a + b; }
```

Plain functions over plain data are easier to test, easier to tree-shake, and avoid `this` entirely — which removes a whole class of binding bug. Use classes where they earn it: entities with invariants, stateful services, and anywhere a framework expects them.

## Exercise

```typescript
// Model a `Playlist`:
//   - readonly id, private tracks array
//   - add(track) rejecting duplicates by id
//   - a `duration` getter returning total seconds
//   - implements an interface `Sized { readonly size: number }`
// Use parameter properties for the constructor.

// write your code here
```

## Common questions

### `private` or `#`?

`#` when you want the field genuinely unreachable at runtime, `private` when you only want the compiler to stop your own code touching it. `#` is real JavaScript and survives compilation; `private` is erased.

### Should I use `implements` on every class?

Only where an interface is a meaningful contract — something with more than one implementation, or a boundary you want stated explicitly. Structural typing means a matching class works either way; `implements` mainly improves *where* the error appears when the class drifts.

### Are parameter properties safe to use?

They are convenient and widely used, especially with dependency injection. Be aware they are TypeScript-only syntax, so they break under `erasableSyntaxOnly` and runtime type-stripping. If you need your `.ts` files to run without a compile step, write the assignments out.
