๐ŸŸจ JavaScript Basics

Modern JavaScript: 10 ES2024+ Features You Should Be Using

By Shubham Sharmaยทยท13 min read
Modern JavaScript code snippets stacked vertically

JavaScript has changed more in the last five years than in the previous twenty. When I look back at the JS I wrote in 2019 it feels almost archaic โ€” nested ternaries, lodash.get for safe access, JSON.parse(JSON.stringify(x)) for deep clones. Here are the ten modern features that genuinely make my code shorter, safer, and easier to read on real client projects โ€” with the before/after that convinced me to switch.

1. Optional chaining โ€” kill the null checks

// old
const city = user && user.address && user.address.city;

// modern
const city = user?.address?.city;

// also works with function calls and arrays
user?.getName?.();
users?.[0]?.name;

2. Nullish coalescing (??) โ€” smarter than ||

const count = data.count || 10;   // wrong: 0 becomes 10
const count = data.count ?? 10;   // right: only null/undefined trigger fallback

3. Logical assignment operators

settings.theme ??= "light";  // assign if null/undefined
user.count ||= 1;            // assign if falsy
feature.enabled &&= isProd;  // reassign only if truthy

4. structuredClone โ€” real deep copies, built in

// old
const copy = JSON.parse(JSON.stringify(obj)); // loses Dates, Maps, undefined

// modern
const copy = structuredClone(obj); // handles Dates, Maps, Sets, typed arrays

5. Array.prototype.at(-1) โ€” the last element, finally

arr[arr.length - 1];   // ugh
arr.at(-1);            // clean
arr.at(-2);            // second to last

6. Object.groupBy โ€” group an array by a key

const users = [
  { name: "A", role: "admin" },
  { name: "B", role: "user"  },
  { name: "C", role: "admin" },
];

const byRole = Object.groupBy(users, u => u.role);
// { admin: [ {A}, {C} ], user: [ {B} ] }

7. Top-level await in ES modules

// inside a .mjs or type="module" file โ€” no wrapping async function needed
const config = await fetch("/config.json").then(r => r.json());
export default config;

8. Promise.withResolvers() โ€” cleaner deferred promises

const { promise, resolve, reject } = Promise.withResolvers();
btn.addEventListener("click", () => resolve("clicked"), { once: true });
await promise;

9. String.prototype.replaceAll

"a-b-c".replaceAll("-", "_");  // "a_b_c" โ€” no regex needed

10. Private class fields with #

class Counter {
  #count = 0;                // truly private, not a naming convention
  increment() { this.#count++; }
  get value()  { return this.#count; }
}

Bonus: features I use less often but love

  • Array.prototype.toSorted / toReversed โ€” non-mutating sort/reverse
  • Set.prototype.union / intersection / difference (2024)
  • Iterator helpers โ€” .map/.filter/.take on any iterator without arrayifying
  • RegExp /v flag โ€” set operations inside character classes

Frequently asked questions

Do I need a transpiler for these?

All major evergreen browsers support items 1โ€“10. Iterator helpers and Set methods are newer โ€” check caniuse.com if you support older Safari. For typical greenfield projects in 2026 you can drop Babel entirely.

Will old code still work?

Yes โ€” none of these features remove anything. Everything above is additive, so incremental adoption is safe.

What's the difference between ?? and ||?

|| triggers on any falsy value (0, '', false, null, undefined). ?? triggers only on null or undefined. Use ?? for defaults that should allow 0 or empty strings.

Is structuredClone available in Node.js?

Yes โ€” global since Node 17. Also in Deno, Bun, Cloudflare Workers, and all modern browsers.

Enjoyed this article?

Share it with a fellow developer or explore more tutorials in our blog.

More articles

Related articles