JavaScript ES2024+ Features Worth Using (and What to Skip)
JavaScript ships new language features every year. Most are small, some are transformative, and a few are answer-existing-pain wins you should adopt today. This article walks through the ES2023, ES2024, and ES2025 additions worth knowing — what to use immediately, what to wait on, and what to skip.
This is Part 18 — the final part of the JavaScript Fundamentals series. If you've made it through the previous 17, you're now better equipped than 90% of working JS developers to read, debug, and architect modern code.
ES2023 highlights (universally supported) #
Non-mutating array methods #
Before ES2023, arr.sort(), arr.reverse(), and arr.splice() all mutated the array. ES2023 added immutable equivalents:
const arr = [3, 1, 2];
const sorted = arr.toSorted(); // [1, 2, 3] — arr unchanged
const reversed = arr.toReversed(); // [2, 1, 3] — arr unchanged
const spliced = arr.toSpliced(1, 1); // [3, 2]
const replaced = arr.with(0, 99); // [99, 1, 2] — replace index 0
Four names, one mental model: "non-mutating version of the classic method." Browser + Node support is universal as of 2024. Use these. Half of immutability boilerplate disappears overnight.
Array.prototype.findLast and findLastIndex #
[1, 2, 3, 4].findLast(n => n < 3); // 2
[1, 2, 3, 4].findLastIndex(n => n < 3); // 1
Finally — no more arr.slice().reverse().find(...). Universal support.
Hashbang grammar #
#!/usr/bin/env node
console.log('Hello from a Node script');
The #! on the first line is now valid JS syntax. Node already supported it; the spec catches up. Useful only for CLI scripts.
Symbols as WeakMap keys #
Covered in Part 10. Non-registered Symbols can now key WeakMaps, enabling tag-without-wrapper patterns.
ES2024 highlights (well-supported as of 2026) #
Object.groupBy and Map.groupBy #
The one you've been hand-rolling forever:
const items = [
{ type: 'fruit', name: 'apple' },
{ type: 'veg', name: 'carrot' },
{ type: 'fruit', name: 'banana' },
];
Object.groupBy(items, item => item.type);
// {
// fruit: [{ type: 'fruit', name: 'apple' }, { type: 'fruit', name: 'banana' }],
// veg: [{ type: 'veg', name: 'carrot' }],
// }
Map.groupBy(items, item => item.type);
// Map { 'fruit' => [...], 'veg' => [...] }
The Map.groupBy version is essential when you want object keys (not just strings) for the groups:
Map.groupBy(items, item => item.category); // category is an object reference
Replaces 4-line reduce boilerplate across thousands of codebases. Use it.
Promise.withResolvers #
A cleaner alternative to the classic deferred pattern:
// Before — assigning resolve/reject inside an executor:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// After:
const { promise, resolve, reject } = Promise.withResolvers();
Replaces a 5-line pattern with one line. Useful for queues, event-driven adapters, and anywhere you need to resolve a promise from outside its executor.
Well-formed Unicode strings #
const s = '\uD800'; // unpaired surrogate — was invalid UTF-16
s.isWellFormed(); // false
s.toWellFormed(); // 'A' (replacement char) — safe to serialize
JSON.stringify(s); // would have thrown in some environments
Narrow but useful for code that handles untrusted strings (emoji parsing, anti-spam, log normalization).
ES2025 (just shipped or shipping) #
Iterator helper methods #
Iterators get the same map, filter, take, drop, flatMap, forEach, toArray, reduce that arrays have — but lazily:
function* naturals() {
let n = 1; while (true) yield n++;
}
naturals()
.filter(n => n % 2 === 0)
.map(n => n * n)
.take(5)
.toArray();
// [4, 16, 36, 64, 100]
The equivalent today (without these helpers) requires building your own generator pipeline. With them, generators get array-like ergonomics with lazy semantics. Huge for stream processing and infinite sequences. Browser support is growing in 2026.
Set methods (union, intersection, difference, etc) #
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
a.union(b); // Set { 1, 2, 3, 4 }
a.intersection(b); // Set { 2, 3 }
a.difference(b); // Set { 1 }
a.symmetricDifference(b);// Set { 1, 4 }
a.isSubsetOf(b); // false
a.isSupersetOf(b); // false
a.isDisjointFrom(b); // false
Finally. Set arithmetic without lodash. Use it.
RegExp.escape #
const userInput = 'foo.bar?';
const re = new RegExp(`^${RegExp.escape(userInput)}$`); // safely match literal
The pattern of escaping regex special chars used to require a hand-rolled helper. Now built in.
Stage-3 proposals worth watching #
Not yet shipped but well-supported in toolchains:
Decorators #
Class and method decorators are now Stage 3 and shipping in TypeScript 5+:
@logged
class Service {
@memoized
async fetch(id) { /* ... */ }
}
React, Angular (legacy decorators), MobX, NestJS — many ecosystems already use them. Native support is rolling out.
Pipeline operator |> #
Long-debated proposal that adds:
result = value |> double |> square |> negate;
Reads like pipe(...) from Part 17 but built into the language. Still Stage 2; ship date unclear.
Records and Tuples #
Deeply immutable, value-equality versions of objects and arrays:
const point1 = #{ x: 1, y: 2 };
const point2 = #{ x: 1, y: 2 };
point1 === point2; // true — value equality!
Game-changer for React, Redux, and any state-comparing system. Stage 2 — track but don't depend on it yet.
What you can skip (for now) #
- Atomics + SharedArrayBuffer — niche worker-thread features. Skip unless you're writing high-perf computation across workers.
TemporalAPI — replaces the awfulDateAPI. Stage 3, browser support landing in 2026. Watch this one — when it ships, switch immediately. Until then, usedate-fnsordayjs.- Explicit Resource Management (
using) — Stage 3. Adds ausingkeyword that calls a cleanup method when the variable goes out of scope. Useful for streams, locks, transactions. Worth adopting once your runtime supports it. - Realms — for isolating JS contexts. Stage 2; rarely useful in app code.
What 2026 best-practice looks like #
Given everything across this series, the modern style guide:
constdefault,letwhen reassigning, nevervar. (Part 2)===always,== nullonly. (Part 6)- Arrow functions for callbacks;
functionfor methods that needthis. (Part 5) async/awaitover.thenchains. (Part 8)- Non-mutating array methods:
toSorted,toReversed,toSpliced,with,findLast. (this article) Object.groupBy/Map.groupByinstead of hand-rolledreduce. (this article)- Compose with
pipe, not deep call nesting. (Part 17) - Pure cores, impure shells. (Part 16)
- WeakMap for per-object metadata; class
#fieldsfor hard privacy. (Part 10) Error.causewhen re-throwing. (Part 11)- Always use modules; never
varglobals. (Part 3) - Generators for lazy streams; async generators for async streams. (Part 15)
Apply these and your code looks current in 2026.
Common gotchas #
Gotcha 1: Polyfill check the new methods before assuming support.
if (typeof Object.groupBy !== 'function') { /* polyfill */ }
Most modern environments support ES2024 but a stray older Node or embedded engine will still bite.
Gotcha 2: toSorted returns a NEW array — don't forget to assign.
arr.toSorted(); // value discarded
const sorted = arr.toSorted(); // ✓
Gotcha 3: Promise.withResolvers is not a constructor.
new Promise.withResolvers(); // TypeError
Promise.withResolvers(); // ✓
Gotcha 4: Object.groupBy returns a plain object (string keys); Map.groupBy returns a Map (any keys).
If your group key is an object, you must use Map.groupBy. Object.groupBy will silently coerce object keys to '[object Object]'.
Closing the series #
If you read every part of this series, here's what you've covered:
- Execution Context & Call Stack — the mental model behind everything else
- Hoisting Demystified — the creation phase and TDZ
- Lexical Scope — how variable lookups work
- Closures — functions that remember their environment
- The
thisKeyword — five binding rules - Type Coercion & Equality —
==vs===, falsy/truthy - The Event Loop — microtasks vs macrotasks
- Promises from Scratch — the 60-line implementation
- Memory & Garbage Collection — leak patterns and DevTools
- WeakMap, WeakSet, WeakRef — weak references explained
- Error Handling Patterns — async, Result types, Error.cause
- Prototypes & Prototype Chain —
classdecoded - Inheritance Patterns — classes vs composition vs mixins
- Symbols — the 7th primitive
- Iterators & Generators — lazy sequences
- Pure Functions & Side Effects — the functional-core/imperative-shell pattern
- Higher-Order Functions & Composition —
pipe,compose, currying - ES2024+ Worth Using — modern syntax + this best-practice cheat sheet
That's the whole stack. Every other JavaScript topic — frameworks, build tools, type systems, runtime APIs — builds on these.
Recap #
- ES2023 —
toSorted,toReversed,toSpliced,with,findLast. Use immediately. - ES2024 —
Object.groupBy,Map.groupBy,Promise.withResolvers. Use immediately. - ES2025 — iterator helpers, Set methods,
RegExp.escape. Use as runtime support catches up. - Stage 3 watch list — decorators (use via TS today),
Temporal,using. - The 2026 JS style guide is short:
const,===, async/await, immutable arrays, pure cores, modules.
Thanks for reading the series. If something landed (or didn't), let us know — every article above gets updated as the language evolves.
Up next in JavaScript
More from this topic
Enjoyed this article?
Get new JavaScript tutorials delivered. No spam — just code-first articles when they ship.


