JavaScript & the runtime

Modern JavaScript, ES2024 to ES2026

What each of the last three editions added, what is finished for ES2027, the stage 3 proposals worth knowing by name, and how Baseline, build targets and polyfills decide what you can ship unpolyfilled.

"What is new in JavaScript" is a warm-up that separates candidates who read release notes from those who learned the language once. ES2026 was ratified on 30 June 2026; knowing what it contains, what just missed it, and how to decide when a feature is safe to use is the senior version of the answer.

Know

  • ES2023: findLast/findLastIndex, change-by-copy arrays (toSorted, toReversed, toSpliced, with), hashbang grammar, symbols as WeakMap keys.
  • ES2024: Promise.withResolvers(), Object.groupBy/Map.groupBy, resizable and growable ArrayBuffer plus transfer(), RegExp v flag (set notation, string properties), Atomics.waitAsync, String.prototype.isWellFormed/toWellFormed.
  • ES2025: Set methods (union, intersection, difference, …), sync iterator helpers, RegExp.escape, Promise.try, JSON modules and import attributes (with { type: 'json' }), RegExp pattern modifiers ((?i:…)), duplicate named capture groups in alternation, Float16Array and Math.f16round, redeclarable eval-introduced globals.
  • ES2026 (ratified June 2026): Upsert (Map.prototype.getOrInsert, getOrInsertComputed, also on WeakMap), Iterator.concat (iterator sequencing), Uint8Array base64 and hex (fromBase64, toBase64, fromHex, toHex), Math.sumPrecise (exact summation of doubles), Error.isError, Array.fromAsync, and JSON.parse source text access (JSON.rawJSON, JSON.isRawJSON, reviver context.source for exact big numbers).
  • Finished for ES2027 (stage 4 after the 2026 cut-off): Explicit Resource Management (using/await using, Symbol.dispose/asyncDispose, DisposableStack), Temporal, joint iteration (Iterator.zip, zipKeyed), Atomics.pause. Shipping in engines ahead of the spec year.
  • Stage 3 worth naming: decorators (in TypeScript for years, engines still landing), import defer, import source/source phase imports, Intl.DurationFormat (shipped broadly), and the Signals proposal at stage 1 as the cross-framework reactivity primitive.
  • Baseline is the shared vocabulary for support: Newly available when the last major engine ships it, Widely available 30 months later. Vite's default build.target is 'baseline-widely-available' (since Vite 7), so the build transpiles syntax below that line and leaves newer runtime APIs to polyfills.
  • Syntax versus API: transpilers (esbuild, Rolldown/Oxc, Babel) downlevel syntax (using, decorators, class fields) but cannot add runtime APIs (Iterator.concat, Map.getOrInsert); those need polyfills (core-js) or a runtime check. Node's version fixes what the server has; the browser list fixes what the client has.
  • TypeScript is not JavaScript's release notes: TS 7 (July 2026) types the new APIs via lib (es2026) and downlevels syntax by target, but a lib entry does not make the API exist in an old browser.
  • Housekeeping worth knowing: Array.prototype.sort stable since ES2019, optional catch binding, ??=/||=/&&=, top-level await (ES2022), class static blocks and #private (ES2022), Object.hasOwn, at(), structuredClone (web API, not ECMAScript), Error cause option.

How it works

ES2026 in one file:

es2026.ts
// Upsert: no more has/set dance
const groups = new Map<string, string[]>()
for (const user of users) groups.getOrInsertComputed(user.team, () => []).push(user.name)

// Iterator sequencing
const everything = Iterator.concat(activeUsers, invitedUsers).map(u => u.email).toArray()

// Binary <-> base64/hex without btoa hacks
const bytes = new TextEncoder().encode('héllo')
const b64 = bytes.toBase64()                  // 'aMOpbGxv'
const back = Uint8Array.fromBase64(b64)

// Exact summation of doubles: [0.1, 0.2, 0.3].reduce(...) is 0.6000000000000001
Math.sumPrecise([0.1, 0.2, 0.3])              // 0.6

// Cross-realm safe error check
Error.isError(caught)

// Collect an async iterable
const rows = await Array.fromAsync(readLines(file))

// Big numbers survive JSON round-trips via source text access
const parsed = JSON.parse('{"id": 9007199254740993}', (key, value, context) =>
  key === 'id' ? BigInt(context.source) : value)
JSON.stringify({ id: JSON.rawJSON('9007199254740993') }) // '{"id":9007199254740993}'

ES2027's headliners, already usable through TypeScript downlevelling or polyfills:

es2027.ts
// Explicit Resource Management: dispose in reverse order at block exit, even on throw
{
  using conn = await pool.connect()          // conn[Symbol.asyncDispose] or [Symbol.dispose]
  using tx = conn.begin()
  await tx.run(query)
} // tx disposed, then conn released

// Temporal: no more Date arithmetic bugs
const start = Temporal.PlainDate.from('2026-09-21')
const due = start.add({ weeks: 2 })          // 2026-10-05, calendar-aware
const zoned = Temporal.ZonedDateTime.from('2026-09-21T10:00[Europe/Prague]')
zoned.withTimeZone('America/New_York').toString()

// Joint iteration
for (const [name, score] of Iterator.zip([names, scores])) console.log(name, score)

Deciding what to ship, in configuration:

nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    build: { target: 'baseline-widely-available' }, // syntax below this line is downlevelled by Oxc/esbuild
  },
  nitro: { esbuild: { options: { target: 'node24' } } }, // the server's Node fixes what it can run
})
app/plugins/polyfills.client.ts
// Runtime APIs need polyfills, not transpiling. Load only when missing.
export default defineNuxtPlugin(async () => {
  if (!('getOrInsert' in Map.prototype)) await import('core-js/actual/map/get-or-insert')
  if (!('toBase64' in Uint8Array.prototype)) await import('core-js/actual/typed-array/to-base64')
})
Gotcha· It compiles, so it works

TypeScript with lib: ['es2026'] happily type-checks map.getOrInsert(), and esbuild passes it through untouched because it is an API, not syntax. On a browser two versions behind, that is a TypeError in production. Check Baseline status (or caniuse) for APIs and let the build target handle syntax.

Gotcha· Temporal is finished, not universal

Temporal reached stage 4 with an ES2027 publication year and ships in Firefox and behind flags or partially elsewhere; production use in 2026 means the polyfill (temporal-polyfill) and a bundle-size decision. Say "finished proposal, shipping progressively" rather than "part of ES2026".

Verify before the interview:

The exact ES2026 list and the ES2027 pipeline (Explicit Resource Management, Temporal, Joint Iteration, Atomics.pause), plus browser Baseline status of Temporal and using as of the interview week.

docs ↗

Exercise

Exercise
  • Run the ES2026 snippet in the current Node LTS and in your browser; note which lines need a polyfill where.
  • Enable erasableSyntaxOnly-friendly TypeScript settings and write a using block with a DisposableStack; check what the emitted JavaScript looks like at target: es2022.
  • Look up three features on web.dev/baseline and classify each as Newly or Widely available; decide for each whether your product would ship it unpolyfilled.

Be able to say

Be able to say· What did the last few ECMAScript editions add?

"ES2024 brought Promise.withResolvers, Object.groupBy, resizable ArrayBuffers and the RegExp v flag. ES2025 added Set algebra methods, iterator helpers, RegExp.escape, Promise.try, JSON modules with import attributes and Float16Array. ES2026, ratified this June, added Map upsert with getOrInsert, Iterator.concat, base64 and hex on Uint8Array, Math.sumPrecise, Error.isError, Array.fromAsync and JSON source-text access for exact big numbers. Already finished for 2027 are Explicit Resource Management with using, Temporal and joint iteration. In practice I decide by Baseline status: syntax is downlevelled by the build target, runtime APIs need polyfills until they are widely available."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.