override

Marks a member as deliberately replacing a base-class member, so renaming the base becomes a compile error.

Signature
class Derived extends Base {
  override member(): void { }
  override readonly prop: Type;
  override get accessor(): Type { }
}

// tsconfig.json
{ "compilerOptions": { "noImplicitOverride": true } }
  • Added in TypeScript 4.3

Parameters

ParameterTypeDescription
$membermethod | property | accessorThe derived-class member being marked. It must exist on the base class, otherwise the modifier itself is an error.
$noImplicitOverrideoptionalboolean= falseCompiler option. When true, every member that replaces a base-class member must carry the modifier, and an unmarked one is an error.

Errors

Marking a member that the base class does not declare is an error: "This member cannot have an 'override' modifier because it is not declared in the base class". Under noImplicitOverride, omitting it on a member that does replace a base member is also an error.

Changelog

  • PHP 4.3The override modifier and the --noImplicitOverride flag were introduced together.

The modifier states an intention: this member exists because the base class declares one with the same name. TypeScript then checks the claim in both directions, which turns a silent breakage into a compile error.

The failure it prevents

A subclass defines hide() to replace the base's hide(). Somebody later renames the base method to setVisible(). Nothing complains: the subclass's hide() is now a perfectly valid new method that nobody calls, and the base's behaviour quietly takes over. Marked with the modifier, that rename fails to compile everywhere it matters.

When to reach for it

  • Deep class hierarchies you do not own end to end — framework base classes, generated clients, plugin APIs.
  • Any codebase where a base class and its subclasses live in different packages and move on different schedules.

What trips people up

The modifier alone is opt-in and only catches one direction: it errors when the base member is missing. To catch the accidental shadow — a subclass member that collides with a base member you never meant to touch — you need noImplicitOverride, and that flag is not included in strict. Turning it on in an existing project produces one error per unmarked member; a codemod, not a manual pass, is the right tool. Note also that it applies to inheritance only, never to interface implementation: a member satisfying implements must not be marked.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Guard subclasses against a base class in another package renaming or removing a method.
  • Enable noImplicitOverride so a new helper on a base class cannot silently collide with an existing subclass method.
  • Document at a glance which members of a long subclass are specialisations and which are new.

Frequently asked questions

Do I need the override keyword in TypeScript?

Not by default — it is optional until you enable noImplicitOverride, which is not part of strict. Use it on any class that extends a base you do not control, because it turns a renamed or deleted base member into a compile error instead of a silently orphaned method.

Why does TypeScript say a member cannot have an override modifier?

Because the base class does not declare a member with that name — usually it was renamed, removed, or you are extending a different base than you think. It also appears when you mark a member that only satisfies an `implements` clause, since interfaces are not overridden.

What is the difference between override and noImplicitOverride?

The modifier is a per-member assertion that a base member exists; noImplicitOverride is the compiler option that makes the modifier mandatory on every member that replaces a base member. Without the flag you catch deleted base members; with it you also catch accidental name collisions.

More examples

The error you are buying

Type-stripping still runs this, so it prints "data". The point is what tsc reports: the marked member no longer matches anything in Store, so the rename surfaces as an error at the exact site that needs updating rather than as a behaviour change in production.

Example · typescript
class Store {
  // Renamed from `load()` in v2 of the library.
  fetch(): string { return "data"; }
}

class CachedStore extends Store {
  override load(): string { return "cached"; }
  //       ~~~~
  // Error: This member cannot have an 'override' modifier because it is not
  // declared in the base class 'Store'.
}

// Without the modifier this compiles fine, and `load()` becomes dead code
// while every caller silently gets Store.fetch() instead.
console.log(new Store().fetch());

noImplicitOverride and accidental shadowing

Prints "hello-world". Under the flag, the unmarked normalize is an error that forces a decision: either it is a deliberate specialisation (add the modifier) or the name collided by accident (rename it). slugify needs no modifier because Base declares nothing by that name.

Example · typescript
// tsconfig.json: { "compilerOptions": { "noImplicitOverride": true } }

class Base {
  protected normalize(value: string): string {
    return value.trim().toLowerCase();
  }
}

class Derived extends Base {
  // Error under noImplicitOverride:
  // This member must have an 'override' modifier because it overrides a
  // member in the base class 'Base'.
  protected normalize(value: string): string {
    return value.toUpperCase();
  }

  // Fine: `slugify` is genuinely new, so no modifier is wanted.
  slugify(value: string): string {
    return super.normalize(value).replace(/\s+/g, "-");
  }
}

console.log(new Derived().slugify("  Hello World  "));

Discussion

  • Be the first to comment on this lesson.