⚡

Modern JavaScript Features Worth Using Every Day

JavaScript3 min readUpdated October 2026

A hands-on tour of the modern JavaScript features that make everyday code shorter and safer, from destructuring and ?. to structuredClone and toSorted.

JavaScript has changed a lot since 2015. Many habits from old tutorials, like var, string concatenation, and manual null checks, have better replacements. Here are the features that pay off the most, each with a short example you can run.

const and let, never var

Use const by default. Switch to let only when the value has to change. Both are block-scoped, which avoids a whole family of bugs var caused.

const limit = 3;
let total = 0;

for (let i = 0; i < limit; i++) {
  total += i;
}
console.log(total);
3

const doesn't make objects frozen. It only stops you from reassigning the name. You can still change what's inside an object or array.

Template literals

Backticks let you embed values and write multi-line strings without +.

const name = "Ana";
const items = 3;
console.log(`Hi ${name}, you have ${items} item${items === 1 ? "" : "s"} in your cart.`);
Hi Ana, you have 3 items in your cart.

Destructuring

Pull values out of objects and arrays in one line.

const user = { name: "Ben", role: "admin", address: { city: "Toronto" } };

const { name, role } = user;
const { address: { city } } = user;
console.log(name, role, city);

const [first, second = "none"] = ["a"];
console.log(first, second);
Ben admin Toronto
a none

It's especially nice in function parameters, where you can name the options you care about and give them defaults:

function createButton({ label, size = "medium", disabled = false }) {
  return `${label} (${size}${disabled ? ", disabled" : ""})`;
}

console.log(createButton({ label: "Save" }));
console.log(createButton({ label: "Delete", disabled: true }));
Save (medium)
Delete (medium, disabled)

Spread and rest

Three dots do two related jobs. In a call or literal they spread things out. In a parameter list they gather things up.

const defaults = { theme: "light", fontSize: 14 };
const custom = { fontSize: 18 };
const settings = { ...defaults, ...custom };
console.log(settings);

const nums = [1, 2, 3];
const more = [...nums, 4, 5];
console.log(more);

function sum(...values) {
  return values.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4));
{ theme: 'light', fontSize: 18 }
[ 1, 2, 3, 4, 5 ]
10

Spreading makes a shallow copy. For a deep copy, use structuredClone, covered below.

Optional chaining and nullish coalescing

These two replace a lot of defensive if statements.

const profile = { name: "Cy", social: null };

console.log(profile.social?.twitter);
console.log(profile.address?.city ?? "Unknown city");
console.log(profile.getAge?.() ?? "no getAge method");
undefined
Unknown city
no getAge method

?. stops and returns undefined if anything along the path is null or undefined. ?? supplies a fallback only for null or undefined.

That's different from ||, which replaces any falsy value. This matters with numbers and empty strings:

const volume = 0;
console.log(volume || 50);
console.log(volume ?? 50);
50
0

With ||, a volume of zero becomes 50, which is almost never what you want.

Array methods that save loops

const people = [
  { name: "Ana", age: 31 },
  { name: "Ben", age: 17 },
  { name: "Cy", age: 45 },
];

const adults = people.filter((p) => p.age >= 18);
const names = adults.map((p) => p.name);
const total = adults.reduce((sum, p) => sum + p.age, 0);

console.log(names, total);
console.log(people.find((p) => p.age > 40));
console.log(people.some((p) => p.age < 18));
console.log(people.every((p) => p.age < 18));
[ 'Ana', 'Cy' ] 76
{ name: 'Cy', age: 45 }
true
false

Newer additions are worth knowing too:

const list = [5, 1, 4];

console.log(list.at(-1));
console.log(list.toSorted((a, b) => a - b));
console.log(list);
console.log([[1, 2], [3, [4]]].flat(2));
4
[ 1, 4, 5 ]
[ 5, 1, 4 ]
[ 1, 2, 3, 4 ]

at(-1) gets the last item. toSorted returns a sorted copy and leaves the original alone, unlike sort, which changes the array in place.

Objects: entries, fromEntries, and structuredClone

const prices = { apple: 1, pear: 2 };

const doubled = Object.fromEntries(
  Object.entries(prices).map(([item, price]) => [item, price * 2])
);
console.log(doubled);

const original = { tags: ["a"], meta: { views: 1 } };
const copy = structuredClone(original);
copy.meta.views = 99;
console.log(original.meta.views);
{ apple: 2, pear: 4 }
1

structuredClone makes a true deep copy, so changing the copy never affects the original.

Arrow functions and the this gotcha

Arrow functions are shorter, and they don't create their own this. That's great for callbacks and a trap for object methods.

const timer = {
  label: "Timer A",
  describeAll() {
    return ["x", "y"].map((item) => `${this.label}: ${item}`);
  },
};

console.log(timer.describeAll());
[ 'Timer A: x', 'Timer A: y' ]

Use arrow functions for callbacks. Use regular method syntax (sayHi() { ... }) for methods on objects and classes.

Key takeaways

  • const first, let when needed, never var.
  • Destructuring and spread cut a lot of boilerplate.
  • ?. and ?? handle missing data safely. Use ??, not ||, when 0 or "" are valid values.
  • Prefer toSorted and structuredClone when you don't want to change the original.

Try it yourself

Reading helps, but typing the code is what makes it stick. Open the editor, paste any example from this article, and change it until it breaks.

Open the JavaScript editor

Related Articles