ECMAScript 2026 is one of the biggest JavaScript updates in years. From automatic resource cleanup to pattern matching — here's everything you need to know with real code examples.
Every June, TC39 — the technical committee that standardizes JavaScript — publishes a new edition of the ECMAScript specification. ES2026 (ECMAScript 2026) is shaping up to be one of the most significant updates in recent memory, delivering features that JavaScript developers have been requesting for years.
This year's edition introduces practical, day-to-day improvements: automatic resource cleanup
with using, precise math with Math.sumPrecise(), better
error-handling tools, and several Stage 3 proposals like Pattern Matching and the Pipeline
Operator that are nearing completion. Meanwhile, the long-awaited Temporal API — a complete
replacement for the infamous Date object — has reached Stage 4 and is slated
for ES2027.
In this guide, I'll walk through every significant feature landing in ES2026 and the proposals you should start learning today. You'll find real code examples, adoption status, browser support notes, and practical advice on when to use each feature.
using and await using
Stage 4
The single most impactful feature landing in ES2026 is explicit resource management.
If you've ever written try { ... } finally { resource.close(); }, this feature
eliminates that boilerplate entirely.
using Works
The using keyword declares a block-scoped variable whose Symbol.dispose
method is automatically called when the variable goes out of scope — similar to Python's
with statement or C#'s using:
// Before ES2026 — manual cleanup
function readConfig(path) {
const file = fs.openSync(path, 'r');
try {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
} finally {
fs.closeSync(file);
}
}
// With ES2026 — automatic cleanup
function readConfig(path) {
using file = fs.openSync(path, 'r');
return JSON.parse(fs.readFileSync(file, 'utf-8'));
// file is automatically closed when scope exits
}
await using
For asynchronous resources like database connections or file streams, use
await using with Symbol.asyncDispose:
class DatabaseConnection {
async [Symbol.asyncDispose]() {
await this.close();
console.log('Connection closed');
}
async query(sql) { /* … */ }
}
async function getUser(userId) {
await using db = new DatabaseConnection(config);
return db.query(`SELECT * FROM users WHERE id = ${userId}`);
// db.close() is called automatically
}
Resource leaks are one of the most common bugs in production JavaScript applications.
File handles, database connections, and network sockets that aren't properly cleaned up
cause memory leaks and connection pool exhaustion. using eliminates an entire
category of bugs by making resource disposal automatic and deterministic.
Node.js 24+ and Chrome 126+ already support using natively. For older
environments, Babel provides a transform plugin that's included in @babel/preset-env
with the es2026 target.
Stage 4 Every JavaScript developer has encountered floating-point arithmetic quirks:
0.1 + 0.2; // 0.30000000000000004 (not 0.3!)
Math.sumPrecise() introduces a compensated summation algorithm
(Kahan-Babuška-Neumaier) that significantly reduces accumulation errors when summing
arrays of floating-point numbers:
const prices = [0.1, 0.2, 0.3, 0.4];
// Traditional approach — accumulates errors
const naiveSum = prices.reduce((a, b) => a + b, 0);
// 0.1 + 0.2 + 0.3 + 0.4 = 1.0000000000000002
// ES2026 — precise summation
const preciseSum = Math.sumPrecise(prices);
// 0.0 + 0.1 + 0.2 + 0.3 + 0.4 = 1.0 (exact!)
This is a game-changer for financial calculations, statistical operations, and scientific
computing in JavaScript. While it doesn't replace BigInt for exact integer
arithmetic, it significantly improves precision for real-world floating-point scenarios.
Math.sumPrecise() accepts any iterable — arrays, Sets, typed arrays, or
generator output — and returns 0 for empty iterables.
Stage 4
Checking whether a value is an Error instance has been surprisingly tricky in
JavaScript due to cross-realm issues (errors thrown in iframes or across worker boundaries
don't pass instanceof Error):
// Old approach — unreliable
try {
await riskyOperation();
} catch (err) {
if (err instanceof Error) { // fails across realms!
console.error(err.message);
}
// Fallback: duck-typing
if (err && typeof err.message === 'string') {
console.error(err.message);
}
}
// ES2026 — reliable
try {
await riskyOperation();
} catch (err) {
if (Error.isError(err)) {
console.error(err.message);
console.error(err.stack);
}
}
Error.isError() works reliably across realm boundaries (iframes, workers,
VM contexts) and correctly identifies native Error instances, including
subclasses like TypeError, RangeError, and SyntaxError.
It returns false for plain objects that happen to have message
and stack properties.
Stage 3
Pattern Matching is arguably the most anticipated JavaScript feature in years. It introduces
a match expression that lets you destructure and test values against patterns
in a declarative way — similar to Rust's match or Haskell's pattern matching:
// Before — nested if-else or switch
function getShapeArea(shape) {
if (shape.type === 'circle') {
return Math.PI * shape.radius ** 2;
} else if (shape.type === 'rectangle') {
return shape.width * shape.height;
} else if (shape.type === 'triangle') {
return (shape.base * shape.height) / 2;
}
throw new Error('Unknown shape');
}
// With Pattern Matching
function getShapeArea(shape) {
return match (shape) {
{ type: 'circle', radius } => Math.PI * radius ** 2,
{ type: 'rectangle', width, height } => width * height,
{ type: 'triangle', base, height } => (base * height) / 2,
_ => throw new Error('Unknown shape'),
};
}
ES2026 Pattern Matching supports several pattern kinds:
{ name, age }[first, second]42, "hello"_x when x > 100 | false | null// Practical example: handling API responses
function handleResponse(response) {
return match (response) {
{ status: 200, data } => renderSuccess(data),
{ status: 201, data } => renderSuccess(data),
{ status: 204 } => renderDeleted(),
{ status: 400, error } => renderBadRequest(error),
{ status: 401 } => redirectToLogin(),
{ status: 403 } => renderForbidden(),
{ status: 404 } => renderNotFound(),
{ status: 500, error } => renderServerError(error),
_ => renderUnknownError(),
};
}
Pattern Matching is at Stage 3, meaning the API is mostly stable but may see minor changes
before finalization. You can experiment with it today using the Babel plugin
@babel/plugin-proposal-pattern-matching. V8 and SpiderMonkey have published
early implementation work.
|> — Readable Function CompositionStage 3 The Pipeline Operator introduces a clean, left-to-right syntax for chaining function calls. Instead of reading deeply nested expressions inside-out:
// Before — deeply nested, hard to read
const result = formatCurrency(
applyDiscount(
calculateTotal(
getCartItems(userId)
),
promoCode
),
'USD'
);
// With Pipeline Operator — reads top-to-bottom
const result = getCartItems(userId)
|> calculateTotal(%)
|> applyDiscount(%, promoCode)
|> formatCurrency(%, 'USD');
The |> operator pipes the value on its left into the function on its right.
The topic token (%) refers to the piped value — you can use it multiple times,
nest it in deeper expressions, or omit it entirely for unary function calls:
// Unary function — topic is implicit
users
|> filterActive
|> sortByDate
|> toJSON
// Multiple arguments — use topic token
users
|> filterActive(%)
|> sortByDate(%, 'desc')
|> mapToNames(%)
// Nested expressions
fetch('/api/data')
|> await %
|> %.json()
|> console.log
The Pipeline Operator is one of the most intensely debated proposals in TC39 history — multiple competing syntax versions were proposed and vetted over several years. The current proposal (Hack pipes) won consensus in late 2025 and is rapidly progressing toward Stage 4.
Stage 3
import defer allows you to defer the evaluation of a module until it's actually
used, improving startup performance:
// Normal import — evaluated eagerly
import { heavyLibrary } from './heavy-library.js';
// Deferred import — evaluated on first use
import defer { markdownParser } from './markdown-parser.js';
function renderPage(content) {
// The markdown-parser module is only loaded here,
// on the first call to renderPage
return markdownParser.parse(content);
}
This is particularly valuable for large applications where many modules are imported at
the top level but only used conditionally. import defer lets you keep
imports at the top of your file (maintaining readability and static analysis benefits)
without paying the cost of evaluating modules that may never be used on a given page.
Note the difference from import() — dynamic imports are evaluated asynchronously
and at runtime, while import defer is a static declaration that defers
evaluation but preserves the module's static structure for tree-shaking and bundler analysis.
Stage 4 Slated for ES2027 While not quite making it into ES2026, the Temporal API deserves a special mention because it's the most significant JavaScript language addition in a decade and it reached Stage 4 in March 2026. If you're planning a new project, you should start using Temporal today.
JavaScript's Date object has been a source of bugs and frustration since the
language's inception. It's mutable, has no timezone support (the timezone is always local),
has inconsistent parsing, and uses zero-indexed months. Temporal fixes all of this:
// Date — painful and error-prone
const d = new Date(2026, 4, 16); // May 16 — wait, months are 0-indexed!
d.setMonth(5); // Mutates the original — surprise!
console.log(d.toISOString()); // Assumes UTC, but gets parsed as local
// Temporal — explicit and correct
const date = Temporal.PlainDate.from({ year: 2026, month: 5, day: 16 });
// => 2026-05-16 (clear, no index confusion)
const zoned = Temporal.ZonedDateTime.from({
timeZone: 'Europe/Minsk',
year: 2026,
month: 5,
day: 16,
hour: 10,
minute: 0,
});
// => 2026-05-16T10:00:00+03:00[Europe/Minsk]
// Immutable operations
const nextMonth = date.add({ months: 1 });
// date is unchanged — nextMonth is a new instance
// Timezone-aware arithmetic
const now = Temporal.Now.zonedDateTimeISO('America/New_York');
const inMinsk = now.withTimeZone('Europe/Minsk');
const duration = now.until(inMinsk); // Time difference with timezone awareness
You can use the official Temporal polyfill
today in Node.js and all modern browsers. Node.js 24+ ships Temporal behind the
--experimental-temporal flag, and full native support is expected in Node.js 26+.
| Feature | Stage | ES Version | Status |
|---|---|---|---|
Explicit Resource Mgmt (using) |
Stage 4 | ES2026 | Node 24+, Chrome 126+, Babel |
| Math.sumPrecise() | Stage 4 | ES2026 | Node 24+, Chrome 127+, polyfill |
| Error.isError() | Stage 4 | ES2026 | Node 24+, polyfill |
| Pattern Matching | Stage 3 | ES2027 (expected) | Babel plugin, V8 in progress |
Pipeline Operator (|>) |
Stage 3 | ES2027 (expected) | Babel plugin, TC39 consensus |
| Import Defer | Stage 3 | ES2027 (expected) | Babel plugin, bundler support |
| Temporal API | Stage 4 | ES2027 | Polyfill, Node 24+ experimental |
ES2026 features are usable today, but the approach differs by feature:
Use Node.js 24+ for using/await using and Math.sumPrecise()
without any transpilation. For browser applications, add the ES2026 Babel preset:
# Install the Babel plugin
npm install --save-dev @babel/preset-env @babel/plugin-proposal-explicit-resource-management
# In babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: { esmodules: true },
}],
],
plugins: [
'@babel/plugin-proposal-explicit-resource-management',
],
};
Start with Error.isError() and Math.sumPrecise() — they're
drop-in additions that won't break anything. Then incrementally adopt using
in resource-heavy modules (file I/O, database connections, file streams). Save Pattern
Matching and the Pipeline Operator for greenfield code where you can set up the Babel
plugin from the start.
Add the polyfill to your package.json today and start replacing Date usage
incrementally. Temporal is designed so that when native support ships, you simply remove
the polyfill import — the API surface is identical.
ES2026 brings significant changes to JavaScript, and keeping up with the evolving language can be challenging. Whether you're modernizing an existing codebase, starting a new project, or need guidance on the best tooling setup, I can help.
I'm a senior full-stack developer with 20+ years of experience in JavaScript, React, Node.js, and modern web development. Based in Minsk and working worldwide, let's discuss your project.
For a broader perspective on the JavaScript ecosystem, also check my guide on React vs Vue.js vs Angular, my analysis of the 2026 npm supply chain attack, and my Chrome Prompt API guide for browser-native AI integration.
Tell me about your project — I'll recommend the best technology stack and provide a preliminary estimate. Free of charge.