Advanced25 min read

Modern JS Patterns

Combine modern JavaScript features into practical patterns — short-circuit assignment, object tricks, immutable updates, and more.

Short-Circuit Patterns

JavaScript's logical operators can be used for concise conditional patterns beyond simple boolean logic.

Default values with ||:

javascript
const name = userInput || 'Anonymous';
// Returns userInput if truthy, otherwise 'Anonymous'

Nullish default with ??:

javascript
const count = data.count ?? 0;
// Only falls back to 0 if count is null/undefined
// Preserves 0, '', false (unlike ||)

Guard with &&:

javascript
isLoggedIn && showDashboard();
// Only calls showDashboard() if isLoggedIn is truthy

const name = user && user.name;
// Returns user.name if user is truthy, otherwise user (null/undefined)

Logical assignment operators (ES2021):

javascript
let config = {};

config.theme ??= 'light';     // assign if null/undefined
config.debug ||= false;       // assign if falsy
config.retries &&= config.retries - 1; // assign if truthy

Converting to boolean:

javascript
const hasItems = !!items.length;  // 0 → false, 5 → true
const isValid = !!input.trim();   // '' → false, 'hello' → true

Object Patterns

Modern JavaScript provides elegant shortcuts for working with objects.

Shorthand properties — when variable name matches key:

javascript
const name = 'Alice';
const age = 30;

// Instead of { name: name, age: age }
const user = { name, age };

Computed property names:

javascript
const field = 'email';
const obj = { [field]: 'alice@example.com' };
console.log(obj.email); // 'alice@example.com'

// Dynamic keys
const prefix = 'user';
const data = {
  [`${prefix}Name`]: 'Alice',
  [`${prefix}Age`]: 30,
};

Object.assign() vs spread:

javascript
// Both merge objects (spread is preferred)
const merged1 = Object.assign({}, defaults, overrides);
const merged2 = { ...defaults, ...overrides };

Object.freeze() — prevent modifications:

javascript
const config = Object.freeze({ port: 3000, host: 'localhost' });
config.port = 8080; // silently fails (throws in strict mode)

Object.keys/values/entries:

javascript
const user = { name: 'Alice', age: 30 };
Object.keys(user);    // ['name', 'age']
Object.values(user);  // ['Alice', 30]
Object.entries(user); // [['name', 'Alice'], ['age', 30]]

Immutable Update Patterns

In modern JavaScript — especially with React and Redux — you should never mutate state directly. Instead, create new copies with the changes applied.

Updating objects:

javascript
const user = { name: 'Alice', age: 25, city: 'Paris' };

// Create new object with updated age
const updated = { ...user, age: 26 };
console.log(user.age);    // 25 (original unchanged)
console.log(updated.age); // 26

Adding items to arrays:

javascript
const items = ['apple', 'banana'];

const added = [...items, 'cherry'];
// ['apple', 'banana', 'cherry']

const prepended = ['mango', ...items];
// ['mango', 'apple', 'banana']

Removing items from arrays:

javascript
const numbers = [1, 2, 3, 4, 5];
const without3 = numbers.filter(n => n !== 3);
// [1, 2, 4, 5]

Updating items in arrays:

javascript
const todos = [
  { id: 1, text: 'Buy milk', done: false },
  { id: 2, text: 'Clean house', done: false },
];

const toggled = todos.map(todo =>
  todo.id === 1 ? { ...todo, done: true } : todo
);

Updating nested objects:

javascript
const state = { user: { name: 'Alice', address: { city: 'Paris' } } };

const newState = {
  ...state,
  user: {
    ...state.user,
    address: {
      ...state.user.address,
      city: 'London',
    },
  },
};

Functional Patterns

Functional programming concepts are widely used in modern JavaScript.

Pure functions — no side effects, same input always gives same output:

javascript
// Pure
const add = (a, b) => a + b;
const toUpper = str => str.toUpperCase();

// Impure (modifies external state)
let count = 0;
const increment = () => ++count;

Higher-order functions — functions that accept or return functions:

javascript
// Accepts a function
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);

// Returns a function
const multiplier = factor => number => number * factor;
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

Composition — combining functions:

javascript
const pipe = (...fns) => value =>
  fns.reduce((acc, fn) => fn(acc), value);

const processName = pipe(
  str => str.trim(),
  str => str.toLowerCase(),
  str => str.replace(/\s+/g, '-')
);

console.log(processName('  Hello World  ')); // 'hello-world'

IIFE (Immediately Invoked Function Expression) — for one-time initialization:

javascript
const config = (() => {
  const env = 'production';
  return { env, debug: env !== 'production' };
})();

Modern Conveniences

Recent JavaScript versions added several quality-of-life features.

Numeric separators for readability:

javascript
const billion = 1_000_000_000;
const bytes = 0xFF_FF_FF;
const fraction = 0.000_001;

globalThis — universal global object:

javascript
// Works in browsers, Node.js, web workers, etc.
globalThis.myGlobal = 'hello';

structuredClone() — proper deep copy:

javascript
const original = { a: 1, nested: { b: 2 } };
const deep = structuredClone(original);
deep.nested.b = 99;
console.log(original.nested.b); // 2 (unaffected!)

Array.at() — access from the end:

javascript
const arr = ['a', 'b', 'c', 'd'];
console.log(arr.at(-1)); // 'd' (last element)
console.log(arr.at(-2)); // 'c' (second to last)

Object.hasOwn() — safer than hasOwnProperty:

javascript
const obj = { name: 'Alice' };
console.log(Object.hasOwn(obj, 'name'));     // true
console.log(Object.hasOwn(obj, 'toString')); // false

String replaceAll():

javascript
const text = 'foo-bar-baz';
console.log(text.replaceAll('-', '_')); // 'foo_bar_baz'

Modern Patterns in Action

html
<div id="output"></div>

<script>
  // Immutable update
  const user = { name: 'Alice', age: 25, city: 'Paris' };
  const updated = { ...user, age: 26 };

  // Short-circuit default
  const displayName = user.nickname ?? user.name;

  // Shorthand properties
  const x = 10, y = 20;
  const point = { x, y };

  // Deep clone with structuredClone
  const original = { data: { items: [1, 2, 3] } };
  const cloned = structuredClone(original);
  cloned.data.items.push(4);

  // Array.at() for last element
  const colors = ['red', 'green', 'blue'];
  const lastColor = colors.at(-1);

  const output = document.getElementById('output');
  output.innerHTML = `
    <p>Original age: ${user.age}, Updated age: ${updated.age}</p>
    <p>Display name: ${displayName}</p>
    <p>Point: {x: ${point.x}, y: ${point.y}}</p>
    <p>Original items: [${original.data.items}] (not mutated)</p>
    <p>Cloned items: [${cloned.data.items}]</p>
    <p>Last color: ${lastColor}</p>
  `;
</script>

What does `{ ...user, age: 30 }` do?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.