Skip to content
Laviyx Learn

Try

JavaScript course menu

Modern JavaScript Features: ES2015 to ES2025 Highlights

A tour of modern JavaScript features and when they arrived: let and const, arrow functions, template literals, destructuring, classes, modules, promises, async/await, optional chaining, nullish coalescing, private fields, at(), Object.groupBy, structuredClone and more, with how to check browser support.

Advanced8 min readUpdated
On this page

JavaScript is standardised as ECMAScript, and since 2015 a new edition has been published every June. This lesson collects the most useful features of the modern language in one place, many of which you have already used in this course, and shows a few small additions you may not have met.

ES2015 (ES6): the big update#

FeatureExampleLesson
let and constconst total = 0;Variables
Arrow functions(a, b) => a + bArrow Functions
Template literals`Hi ${name}`Strings
Destructuringconst { name } = user;Destructuring
Default and rest parametersfunction f(a = 1, ...rest) {}Parameters
Spread[...a, ...b]Destructuring and Spread
Classesclass Cart {}Classes
Modulesimport { x } from "./x.js"Modules
Promisesfetch(url).then(...)Promises
Map, Setnew Map()Map and Set
for...of, iterators, generatorsfunction* gen() {}Iterators and Generators

ES2016 to ES2019#

JavaScript
console.log(2 ** 10);                                   // ES2016: exponent operator
console.log(["a", "b"].includes("b"));                  // ES2016: includes
console.log(Object.entries({ x: 1, y: 2 }));            // ES2017: Object.entries
console.log("5".padStart(3, "0"));                      // ES2017: padStart
console.log({ ...{ a: 1 }, b: 2 });                     // ES2018: object spread
console.log([1, [2, [3]]].flat(Infinity));              // ES2019: flat
console.log(Object.fromEntries([["k", "v"]]));          // ES2019: fromEntries
Output
1024
true
[ [ 'x', 1 ], [ 'y', 2 ] ]
005
{ a: 1, b: 2 }
[ 1, 2, 3 ]
{ k: 'v' }

ES2017 also added async/await (async and await).

ES2020#

JavaScript
const user = { profile: null, settings: { volume: 0 } };

console.log(user.profile?.photo);              // optional chaining
console.log(user.settings.volume ?? 50);       // nullish coalescing
console.log(2n ** 64n);                        // BigInt

const results = await Promise.allSettled([Promise.resolve(1), Promise.reject(new Error("x"))]);
console.log(results.map((r) => r.status));
Output
undefined
0
18446744073709551616n
[ 'fulfilled', 'rejected' ]

ES2020 also standardised globalThis (the global object in every environment) and dynamic import().

ES2021 and ES2022#

JavaScript
console.log("a-b-c".replaceAll("-", "+"));     // ES2021: replaceAll
console.log(1_000_000 + 1);                   // ES2021: numeric separators

let config = { retries: 0 };
config.retries ||= 3;                         // ES2021: logical assignment
config.timeout ??= 5000;
console.log(config);

console.log([10, 20, 30].at(-1));             // ES2022: at()
console.log(Object.hasOwn({ a: 1 }, "a"));    // ES2022: Object.hasOwn

class Counter {
  #count = 0;                                 // ES2022: private fields
  static create() { return new Counter(); }   // static methods (ES2015) and static fields (ES2022)
  increment() { return ++this.#count; }
}
console.log(Counter.create().increment());
Output
a+b+c
1000001
{ retries: 3, timeout: 5000 }
30
true
1

ES2022 also added top-level await in modules and the cause option for errors (Error Handling).

ES2023 to ES2025#

JavaScript
const scores = [72, 95, 58];
console.log(scores.toSorted((a, b) => b - a), scores);       // ES2023: non-mutating
console.log(scores.findLast((s) => s > 60));                  // ES2023: findLast
console.log(scores.with(0, 100));                             // ES2023: with

const people = [
  { name: "Asha", city: "Pune" },
  { name: "Ravi", city: "Delhi" },
  { name: "Meera", city: "Pune" },
];
console.log(Object.groupBy(people, (p) => p.city));           // ES2024: groupBy

const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
console.log(a.intersection(b), a.union(b).size);              // ES2025: Set methods
Output
[ 95, 72, 58 ] [ 72, 95, 58 ]
95
[ 100, 95, 58 ]
[Object: null prototype] {
  Pune: [ { name: 'Asha', city: 'Pune' }, { name: 'Meera', city: 'Pune' } ],
  Delhi: [ { name: 'Ravi', city: 'Delhi' } ]
}
Set(2) { 2, 3 } 4

(Object.groupBy returns an object without a prototype, which Node.js labels [Object: null prototype]; you use it like any other object.)

Also useful, though not part of ECMAScript itself, is structuredClone(value), provided by browsers and Node.js for deep copies.

Checking support#

Modern browsers update automatically, so features from ES2015 to ES2022 work everywhere current. For the newest features:

  • MDN Web Docs has a browser compatibility table at the bottom of every feature page.
  • caniuse.com shows support across browsers and versions.
  • Build tools such as Babel, esbuild and TypeScript can transpile newer syntax into older syntax for older browsers. Missing built-in functions can be added with polyfills.
  • In Node.js, check the version (node --version) and the Node.js release notes.

Practice#

Practice exercise

Modernise this older code using const/let, an arrow function, template literals, destructuring, ?? and toSorted:

JavaScript
var users = [{ name: "Ravi", age: 31 }, { name: "Asha" }, { name: "Meera", age: 28 }];
var sorted = users.slice().sort(function (a, b) { return a.name < b.name ? -1 : 1; });
for (var i = 0; i < sorted.length; i++) {
  var age = sorted[i].age !== undefined && sorted[i].age !== null ? sorted[i].age : "unknown";
  console.log(sorted[i].name + " (" + age + ")");
}
Show solution
JavaScript
const users = [{ name: "Ravi", age: 31 }, { name: "Asha" }, { name: "Meera", age: 28 }];

const sorted = users.toSorted((a, b) => a.name.localeCompare(b.name));
for (const { name, age } of sorted) {
  console.log(`${name} (${age ?? "unknown"})`);
}
Output
Asha (unknown)
Meera (28)
Ravi (31)

Key takeaway#

Finished this lesson?