JavaScript

JavaScript in 2026: ESNext Features That Are Changing How We Write Code

Explore the latest JavaScript features landing in 2026 including the Pipeline Operator, Pattern Matching, Records & Tuples, Decorators, and Signals — with practical examples for modern web development.

Sameer Sabir
Updated:
11 min read
JavaScriptESNextTC39TypeScriptWeb Development

JavaScript in 2026: ESNext Features That Are Changing How We Write Code

JavaScript continues to evolve at a rapid pace. The TC39 committee has been pushing several exciting proposals to Stage 3 and Stage 4, meaning they're either shipping in browsers now or will be very soon. Let's explore the features that are transforming how we write JavaScript in 2026.

1. The Pipeline Operator (|>)

The pipeline operator allows for cleaner function composition by reading left-to-right instead of inside-out:

// Before: Nested function calls (hard to read)
const result = capitalize(trim(removeSpaces(input)));

// After: Pipeline operator (reads naturally)
const result = input
  |> removeSpaces(%)
  |> trim(%)
  |> capitalize(%);

This is especially powerful with data transformations:

const processedUsers = users
  |> %.filter(user => user.active)
  |> %.map(user => ({ ...user, name: user.name.trim() }))
  |> %.sort((a, b) => a.name.localeCompare(b.name))
  |> %.slice(0, 10);

2. Pattern Matching

Pattern matching brings powerful structural matching to JavaScript, similar to what languages like Rust and Elixir have:

const describe = (value) => match (value) {
  when ({ type: "circle", radius }) -> `Circle with radius ${radius}`,
  when ({ type: "rectangle", width, height }) -> `Rectangle ${width}x${height}`,
  when ({ type: "triangle" }) -> "Triangle",
  when (null) -> "Nothing",
  when (String) -> `String: ${value}`,
  when (Number if value > 0) -> `Positive number: ${value}`,
  default -> "Unknown shape"
};

console.log(describe({ type: "circle", radius: 5 }));
// "Circle with radius 5"

Real-World Use: API Response Handling

const handleResponse = (response) => match (response) {
  when ({ status: 200, data }) -> renderData(data),
  when ({ status: 404 }) -> showNotFound(),
  when ({ status: 401 }) -> redirectToLogin(),
  when ({ status: s if s >= 500 }) -> showServerError(),
  default -> showUnknownError()
};

3. Records and Tuples

Records and Tuples bring immutable data structures to JavaScript:

// Records (immutable objects)
const point = #{ x: 10, y: 20 };
// point.x = 30; // TypeError: Cannot assign to read-only property

// Tuples (immutable arrays)
const coords = #[1, 2, 3];
// coords.push(4); // TypeError: coords.push is not a function

// Deep immutability
const user = #{
  name: "Sameer",
  address: #{
    city: "Lahore",
    country: "Pakistan"
  }
};

Value-Based Equality

The killer feature of Records and Tuples is structural equality:

// Regular objects
{ x: 1, y: 2 } === { x: 1, y: 2 }  // false

// Records
#{ x: 1, y: 2 } === #{ x: 1, y: 2 }  // true!

// This enables using them as Map keys
const cache = new Map();
cache.set(#{ userId: 1, page: 1 }, responseData);
cache.get(#{ userId: 1, page: 1 }); // Returns responseData!

4. Decorators (Stage 3)

Decorators are now standardized and provide a clean way to modify class behavior:

function logged(target, context) {
  const methodName = context.name;
  
  return function (...args) {
    console.log(`Calling ${methodName} with`, args);
    const result = target.call(this, ...args);
    console.log(`${methodName} returned`, result);
    return result;
  };
}

function cached(target, context) {
  const cache = new Map();
  
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = target.call(this, ...args);
    cache.set(key, result);
    return result;
  };
}

class MathService {
  @logged
  @cached
  fibonacci(n) {
    if (n <= 1) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }
}

Auto-Accessor Decorators

function validate(min, max) {
  return function (target, context) {
    return {
      set(value) {
        if (value < min || value > max) {
          throw new RangeError(`Value must be between ${min} and ${max}`);
        }
        target.set.call(this, value);
      },
      get() {
        return target.get.call(this);
      }
    };
  };
}

class Product {
  @validate(0, 10000)
  accessor price = 0;

  @validate(0, 100)
  accessor quantity = 0;
}

5. Explicit Resource Management (using)

The using declaration ensures resources are properly cleaned up:

// Database connection
{
  using connection = await database.connect();
  const result = await connection.query("SELECT * FROM users");
  // connection is automatically disposed when block exits
}

// File handling
{
  using file = await openFile("data.json");
  const data = await file.read();
  // file is automatically closed
}

Custom Disposable Resources

class TempDirectory {
  #path;

  constructor(prefix) {
    this.#path = fs.mkdtempSync(prefix);
  }

  get path() {
    return this.#path;
  }

  [Symbol.dispose]() {
    fs.rmSync(this.#path, { recursive: true });
    console.log(`Cleaned up: ${this.#path}`);
  }
}

{
  using tmpDir = new TempDirectory("/tmp/build-");
  // Use tmpDir.path for temporary operations
  await buildProject(tmpDir.path);
  // Automatically cleaned up here
}

6. Iterator Helpers

New methods on iterators for lazy, composable data processing:

function* naturalNumbers() {
  let n = 1;
  while (true) yield n++;
}

// Lazy evaluation - doesn't compute all values upfront
const result = naturalNumbers()
  .filter(n => n % 2 === 0)
  .map(n => n ** 2)
  .take(5)
  .toArray();

// [4, 16, 36, 64, 100]

Working with Async Iterators

async function* fetchPages(url) {
  let page = 1;
  while (true) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();
    if (data.length === 0) return;
    yield* data;
    page++;
  }
}

const activeUsers = await fetchPages("/api/users")
  .filter(user => user.active)
  .take(100)
  .toArray();

7. Temporal API

The long-awaited replacement for the Date object:

// Current date and time
const now = Temporal.Now.plainDateTimeISO();
// 2026-03-01T14:30:00

// Create specific dates
const birthday = Temporal.PlainDate.from("1998-05-15");

// Duration arithmetic
const futureDate = Temporal.Now.plainDateISO().add({ months: 3, days: 15 });

// Time zone handling (the best part!)
const meetingNY = Temporal.ZonedDateTime.from({
  timeZone: "America/New_York",
  year: 2026,
  month: 3,
  day: 15,
  hour: 10,
  minute: 0,
});

const meetingLondon = meetingNY.withTimeZone("Europe/London");
// Automatically converts to London time

// Duration between dates
const until = birthday.until(Temporal.Now.plainDateISO());
console.log(`${until.years} years, ${until.months} months since birthday`);

Using These Features Today

Most of these features can be used today with TypeScript and Babel:

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2024",
    "experimentalDecorators": false, // Use TC39 decorators
    "lib": ["ES2024", "DOM"]
  }
}

For features still in proposal stage, use polyfills:

npm install @babel/plugin-proposal-pipeline-operator
npm install @js-temporal/polyfill

Conclusion

JavaScript in 2026 is more powerful and expressive than ever. The Pipeline Operator makes functional programming more readable, Pattern Matching simplifies complex conditionals, Records & Tuples bring proper immutability, and Decorators provide clean metaprogramming. These features aren't just syntactic sugar — they fundamentally improve code quality and developer productivity. Start experimenting with them today to be ready when they land in all major browsers.

Found this blog helpful? Have questions or suggestions?

Related Blogs