Modern JavaScript features by year

A reference to what each ECMAScript edition since 2015 added, what problem each feature solves, and how to check support before you use it.

  • Beginner
  • 7 min read
  • Updated 17 Oct 2026

What you will learn

  • Recognise modern syntax
  • Check feature support before use
  • Explain how a feature becomes part of the language

Before you start

JavaScript gains new features every year. Knowing roughly when a feature arrived helps you read other people's code, judge whether it will run in the browsers and Node.js versions you support, and spot old workarounds that a built-in now replaces. Use this lesson as a reference rather than reading it top to bottom.

How a feature gets in

The language is standardised as ECMAScript by a committee called TC39. Proposals move through numbered stages, from an idea at stage 0 to finished at stage 4. A feature reaches stage 4 when it has a complete specification, tests and shipping implementations in more than one engine. Each June a new edition is published, named after its year, containing every proposal that reached stage 4 in the preceding months.

Two consequences:

  • Browsers often ship a feature before the edition that contains it is published. The year is when it became official, not when you could first use it.
  • Being in the specification does not mean every browser your users run supports it. Always check.

ES2015 was also called ES6, which is why you will see "ES6" used loosely to mean "modern JavaScript".

ES2015: the big update

ES2015 changed how JavaScript is written. Most of this course uses it everywhere.

FeatureSolves
let and constblock scope instead of function-scoped var
Arrow functionsshort callbacks and lexical this
Classesclear syntax for constructors and inheritance
Template literalsstring interpolation and multi-line strings
Destructuring, default parameters, rest and spread for arrays and argumentsunpacking values and flexible function signatures
Modules (import, export)files with their own scope and explicit dependencies
Promisescomposable asynchronous results
Map, Set, WeakMap, WeakSetreal collections with any key type
Symbol, iterators, generators, for...ofcustom iteration and unique keys
Proxy and Reflectintercepting object operations

ES2016 to ES2019

EditionFeatureExample
ES2016Array.prototype.includestags.includes('css')
ES2016Exponentiation operator2 ** 10
ES2017async and awaitconst data = await load()
ES2017Object.values, Object.entriesObject.entries(scores)
ES2017padStart, padEnd'7'.padStart(3, '0')
ES2018Object rest and spreadconst { id, ...rest } = course
ES2018Async iteration, for await...offor await (const page of pages())
ES2018Promise.prototype.finallyload().finally(hideSpinner)
ES2018Regex named groups, lookbehind, s flag/(?<year>\d{4})/
ES2019flat, flatMap[[1], [2]].flat()
ES2019Object.fromEntriesObject.fromEntries(map)
ES2019trimStart, trimEndinput.trimStart()
ES2019Optional catch bindingtry { ... } catch { ... }

ES2019 also required Array.prototype.sort to be stable, so items that compare as equal keep their original order.

ES2020

FeatureSolves
Optional chaining ?.reading nested values that may be missing
Nullish coalescing ??defaults that keep 0 and ''
BigIntintegers larger than Number.MAX_SAFE_INTEGER
Promise.allSettledwaiting for every promise regardless of failure
globalThisone name for the global object in every environment
Dynamic import()loading a module on demand
String.prototype.matchAlliterating over every regex match with groups
import.metamodule metadata such as its URL
JavaScript
const user = { name: 'Ada' };
const city = user?.address?.city ?? 'Unknown'; // → 'Unknown'
const huge = 2n ** 64n; // → 18446744073709551616n

ES2021

FeatureExample
Promise.any and AggregateErrorfirst successful promise
Logical assignment ||=, &&=, ??=options.retries ??= 3
Numeric separators1_000_000
String.prototype.replaceAllslug.replaceAll(' ', '-')
WeakRef, FinalizationRegistryweak references for specialised caches

ES2022

FeatureExample
Public and private class fields, private methods#count = 0
Static class fields and static blocksstatic instances = 0
Private field check#count in obj
Top-level await in modulesconst data = await fetch(url)
Array.prototype.atitems.at(-1) for the last item
Object.hasOwnsafer than obj.hasOwnProperty(key)
Error causenew Error('Load failed', { cause: error })
Regex match indices, d flagstart and end positions of groups
JavaScript
try {
  JSON.parse('{ bad json');
} catch (error) {
  const wrapped = new Error('Could not read settings', { cause: error });
  console.log(wrapped.cause instanceof SyntaxError); // → true
}

ES2023

FeatureExample
findLast, findLastIndexnewest matching item without reversing
Change array by copy: toSorted, toReversed, toSpliced, withsort without mutating
Hashbang comments#!/usr/bin/env node as the first line of a script
Symbols as WeakMap keysnon-registered symbols as weak keys
JavaScript
const scores = [30, 10, 20];
const sorted = scores.toSorted((a, b) => a - b);
console.log(sorted); // → [ 10, 20, 30 ]
console.log(scores); // → [ 30, 10, 20 ] (unchanged)

ES2024

FeatureExample
Object.groupBy, Map.groupBygroup items by a key
Promise.withResolversa promise plus its resolve and reject functions
Regex v flagset operations and better Unicode in character classes
String.prototype.isWellFormed, toWellFormeddetect and fix lone surrogates
Resizable ArrayBuffer and transfergrowable binary buffers
JavaScript
const courses = [
  { title: 'HTML', level: 'beginner' },
  { title: 'Closures', level: 'intermediate' },
  { title: 'CSS', level: 'beginner' },
];

const byLevel = Object.groupBy(courses, (course) => course.level);
console.log(Object.keys(byLevel)); // → [ 'beginner', 'intermediate' ]
console.log(byLevel.beginner.length); // → 2

Object.groupBy runs in Node.js 21 and later, not Node.js 20, which is a good example of why checking support matters.

ES2025

FeatureExample
Iterator helpersiterator.filter(fn).map(fn).take(5)
Set methodsa.union(b), a.intersection(b), a.difference(b), a.isSubsetOf(b)
Promise.trystart a promise chain from a function that might throw synchronously
RegExp.escapesafely insert user text into a regex
Import attributes and JSON modulesimport data from './data.json' with { type: 'json' }
Regex modifiers, duplicate named groupsfiner control inside patterns
Float16Arrayhalf-precision numbers for graphics and machine learning

Support for the newest features is still spreading, and some are missing from older browsers and from Node.js 20. Later editions continue the pattern. TC39 publishes a list of finished proposals with the edition each one joined, and the specification itself is published at tc39.es.

Checking support

Use these, in order:

  1. MDN compatibility tables. Every MDN reference page ends with a table of browsers, Node.js and Deno versions. It is the quickest check.
  2. Baseline. web.dev and MDN label features as Baseline "newly available" when they work in all major browsers, and "widely available" once that has been true for about two and a half years. Many teams adopt "Baseline widely available" as their default policy.
  3. Your own targets. If you must support a particular older browser or Node.js version, test there. Node.js release notes list the V8 version, which determines its JavaScript support.

Feature detection and fallbacks

For built-in functions and methods, detect them at run time and fall back if they are missing:

JavaScript
function groupBy(items, getKey) {
  if (typeof Object.groupBy === 'function') {
    return Object.groupBy(items, getKey);
  }
  const groups = {};
  for (const item of items) {
    const key = getKey(item);
    (groups[key] ??= []).push(item);
  }
  return groups;
}

The fallback returns an ordinary object, while Object.groupBy returns one with a null prototype. For most uses the difference does not matter.

Syntax cannot be feature-detected this way. If a browser does not understand ??= or a private field, the whole file fails to parse before any of your checks run. For syntax, either keep to what your targets support or use a build tool that transpiles newer syntax to older equivalents and adds polyfills for missing built-ins. Build tools are covered in a frontend tooling course.

Do not assume a newer feature is safe because it works in your own up-to-date browser. Many users run older devices, and company-managed machines often lag behind. Check Baseline status before shipping.

Summary

  • ECMAScript editions are named by year; each contains the proposals that reached stage 4 that year.
  • ES2015 introduced most of modern JavaScript; later editions add smaller, focused features.
  • Recent additions such as Object.groupBy, toSorted, Set methods and iterator helpers replace common hand-written helpers.
  • Check MDN compatibility tables and Baseline status before relying on a feature.
  • Feature-detect built-ins at run time; for new syntax, keep to supported features or transpile.