10 Modern JavaScript Features You Should Know
JavaScript in 2025 isn’t the same beast we were wrestling with back when var
ruled the land and callback hell was just “how things worked.” If you’re still avoiding newer features out of habit—or fear of breaking old code—it’s time for a reality check.
Modern JavaScript is not only more powerful, it’s genuinely enjoyable to write. And trust me, as someone who once debugged an entire app using console.log('here')
, that’s saying something.
Here are 10 modern JavaScript features that every developer should know—and actually use—in 2025.
1. Optional Chaining (?.): Stop Writing Defensive Code
Remember writing a chain of &&
operators just to safely access a nested value? We’ve all done it. And we’ve all hated it.
const theme = user?.profile?.settings?.theme;
With optional chaining, you get cleaner code and fewer runtime surprises. It’s essential for working with dynamic data, especially from APIs that can’t seem to make up their minds.
If you’re building anything that relies on user input or external data, like we do in the JavaScript DOM lessons, this becomes a must-have.
2. Nullish Coalescing (??): Because 0 Isn’t Null
Let’s say 0
is a valid value, but your fallback logic overrides it anyway. That’s ||
messing things up.
const input = 0;
const value = input ?? 100; // Stays 0
Use ??
instead, and stop punishing your perfectly valid falsy values.
3. Top-Level Await: Async Code Without the Dance
Remember when you had to wrap everything in an async
function just to use await
? It felt like doing yoga just to make a fetch call.
Now, thanks to top-level await:
const res = await fetch('https://api.example.com/data');
const data = await res.json();
Perfect for modern module-based apps and Node.js (ESM only), especially when bootstrapping frontend apps with frameworks like Vite or setting up your JS environment.
4. Private Class Fields (#): Actual Encapsulation
Before this, JavaScript’s idea of private was “just don’t touch the property with an underscore, okay?”
Now, we have real private fields:
class Counter {
#count = 0;
increment() {
this.#count++;
return this.#count;
}
}
You can’t even access #count
from outside the class—it’s not just a naming convention anymore.
5. Static Class Blocks: Setup Logic in One Place
Ever needed to initialize complex static properties in a class? Welcome static {}
blocks.
class Config {
static settings;
static {
Config.settings = { theme: 'dark', version: '2.0.5' };
}
}
It’s concise, clean, and excellent for things like configuration bootstrapping or one-time computations.
6. Record & Tuple (Still Stage 2, But Worth Knowing)
JavaScript is getting immutable value types. Think Object.freeze
, but deeper and smarter.
const user = #{ name: 'Alice' };
const list = #[1, 2, 3];
They’re not in the language yet without a polyfill, but they point to a future where deeply immutable structures become a lot easier to use—ideal for functional programming or state management.
Check out TC39’s proposal if you want to dive into the details.
7. Pattern Matching (Proposed): Conditional Logic Without Switch Hell
Still a proposal, but it’s shaping up to be the most expressive way to write conditional logic since destructuring.
match (value) {
{ type: 'admin' } => handleAdmin(),
{ type: 'user' } => handleUser(),
_ => throw new Error('Unknown type'),
}
It’s like a smarter switch
, minus the break statements and boilerplate. If this lands, it’s going to clean up a lot of messy control flow.
8. Array `at()`: Finally, Sanity for Negative Indexes
Who else is tired of writing arr[arr.length - 1]
just to grab the last item?
const last = items.at(-1);
It’s cleaner, easier to read, and feels like something we should’ve had ten years ago. Pair this with array methods from this lesson and your loops start looking a lot more elegant.
9. Object.hasOwn(): A Small Win With Big Benefits
This is a safer and more intuitive version of hasOwnProperty
.
Object.hasOwn(obj, 'key'); // true
It works even if the object was created with Object.create(null)
, where hasOwnProperty
fails. It’s subtle, but it avoids sneaky bugs—especially in libraries or data pipelines.
10. Regex Improvements: Named Groups + `matchAll()`
Regex isn’t always pleasant, but these make it better.
const text = 'Order: #123, #456';
const regex = /#(?<id>\d+)/g;
for (const match of text.matchAll(regex)) {
console.log(match.groups.id);
}
Named capture groups + matchAll()
= readable, maintainable, and way less brittle regex logic.
Stop Writing JavaScript Like It’s 2015
Look, if you’re still avoiding modern features because you think it might break older browsers, you’re living in the past. Most modern JavaScript runtimes support these features natively, and bundlers like Vite or SWC make up the rest.
These features don’t just make your code cleaner—they make it easier to understand, debug, and scale. Whether you’re building a full-stack app or just tweaking some DOM elements, modern JS helps you do more with less.
And let’s be real: if your linter is still mad about using let
instead of var
, it’s time to fire your linter.