TypeScript release

TypeScript 4.3 for JavaScript users

  • Released 26 May 2021
  • Examples run with TypeScript 5.9

TypeScript 4.3 was announced on 26 May 2021. Its release notes are written for .ts files, but several changes reach JavaScript: a setter may accept more types than its getter returns, #private methods and accessors are checked, editors resolve the names in {@link} tags, and a promise tested in an if without await is reported.

It also added the override keyword and noImplicitOverride. In a .js file the keyword is the @override tag, which TypeScript 5.9 checks; the 4.3 notes do not mention the tag, so this site does not date it.

At a glance

ReleaseTypeScript 4.3
Released26 May 2021, the day of its announcement on the TypeScript blog
SourceTypeScript 4.3 release notes
ExamplesEvery example runs through TypeScript 5.9.3 when this site is built.
Previous releaseTypeScript 4.2
Next releaseTypeScript 4.4

What changed for JavaScript

Setters with a wider type than their getters

A set size(value) can now take string | number | boolean while get size() returns number, as long as the getter's type is assignable to the setter's. In JavaScript, type the setter's parameter with @param and the getter with @returns.

size.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: size.js(21,1): error TS2322: Type 'null' is not assignable to type 'string | number | boolean'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. size.js(21,1): error TS2322: Type 'null' is not assignable to type 'string | number | boolean'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. size.js(21,1): error TS2322: Type 'null' is not assignable to type 'string | number | boolean'. checked 21 October 2026
export class Thing {
  #size = 0;

  /** @returns {number} */
  get size() {
    return this.#size;
  }

  /** @param {string | number | boolean} value */
  set size(value) {
    const n = Number(value);
    this.#size = Number.isFinite(n) ? n : 0;
  }
}

const thing = new Thing();
thing.size = "42";
/** @type {number} */
export const size = thing.size;
thing.size = null;error TS2322: Type 'null' is not assignable to type 'string | number | boolean'.
A setter may accept more types than its getter returns.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

noImplicitOverride

With noImplicitOverride on, a method that replaces one from the base class must say so. In a .js file that is the @override tag; without it, TypeScript 5.9 reports TS4119.

shapes.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: shapes.js(12,3): error TS4119: This member must have a JSDoc comment with an '@override' tag because it overrides a member in the base class 'Shape'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. shapes.js(12,3): error TS4119: This member must have a JSDoc comment with an '@override' tag because it overrides a member in the base class 'Shape'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. shapes.js(12,3): error TS4119: This member must have a JSDoc comment with an '@override' tag because it overrides a member in the base class 'Shape'. checked 21 October 2026
export class Shape {
  /** @returns {number} */
  area() {
    return 0;
  }
}

export class Square extends Shape {
  side = 2;

  area() {error TS4119: This member must have a JSDoc comment with an '@override' tag because it overrides a member in the base class 'Shape'.
    return this.side ** 2;
  }
}
With noImplicitOverride, a missing @override is an error.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "noImplicitOverride": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Editors resolve the name inside a {@link} tag, so hovering it shows the target and go to definition jumps there. Nothing checks that the name exists; a broken link is not an error.

A promise used as a condition

Under strictNullChecks, a promise in an if condition is always truthy, so it is reported (TS2801). 4.3 reports a promise held in a variable; 4.4 extends it to a call tested directly.

admin.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: admin.js(9,7): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. admin.js(9,7): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. admin.js(9,7): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined. checked 21 October 2026
/** @returns {Promise<boolean>} */
async function isAdmin() {
  return false;
}

export async function greeting() {
  const admin = isAdmin();
  if (admin) {error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined.
    return "Welcome back";
  }
  return "Hello";
}
A promise in a condition, through a variable.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

#private methods and accessors

Methods, getters and setters can have #private names as well as fields, and static members too. These are JavaScript features, checked like the rest: using one outside its class is an error.

ticket.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: ticket.js(18,8): error TS18013: Property '#format' is not accessible outside class 'Ticket' because it has a private identifier. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. ticket.js(18,8): error TS18013: Property '#format' is not accessible outside class 'Ticket' because it has a private identifier. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. ticket.js(18,8): error TS18013: Property '#format' is not accessible outside class 'Ticket' because it has a private identifier. checked 21 October 2026
export class Ticket {
  static #issued = 0;

  /** @param {string} title */
  constructor(title) {
    Ticket.#issued += 1;
    this.label = this.#format(title);
  }

  /** @param {string} title */
  #format(title) {
    return `#${Ticket.#issued} ${title.trim()}`;
  }
}

const ticket = new Ticket("Broken link");
ticket.#format("again");error TS18013: Property '#format' is not accessible outside class 'Ticket' because it has a private identifier.
A #private method and a static #private counter, called from outside.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

What did not change

@public, @private and @protected work as before; #private names are the run-time alternative.

Upgrade notes

From the breaking changes:

  • Promises tested in conditions are errors under strictNullChecks.
  • useDefineForClassFields defaults to true for target: esnext (and es2022 once 4.6 added that target), which matches how JavaScript class fields work and only changes emitted code.
  • The DOM types dropped APIs no browser implements.

Gotchas

  • Annotate the getter too. If the getter's type is inferred, TypeScript 5.9 treats the property as a string right after you assign a string through the setter, in .ts and .js files alike; @returns {number} on the getter keeps it a number.

Sources

Last reviewed 20 October 2026.