Skip to main content

April 24, 2026 · 9 min read

6 TypeScript and JavaScript tricks most developers still overlook

A practical guide to ??=, satisfies, Object.groupBy, const enum, Array.at(-1), and advanced closure patterns you can ship in production.

Most codebases in 2026 still carry tiny avoidable mistakes: defaulting with ||= when 0 is valid, widening types with as, hand-writing reduce for grouping, or using noisy index math for the last array item.

These are not academic details. They affect real bugs, readability, and long-term maintainability.

Below are five practical tricks from modern JavaScript and TypeScript, plus a bonus closure pattern for more advanced code.

1. Use ??= when falsy values are valid

The assignment operators ||= and ??= look similar, but they solve different problems.

Use ??= for defaults when 0, false, or empty string are legitimate values and should be preserved.

??= vs ||= in real config objects · ts

const settings = {
  retries: 0,
  label: '',
  theme: undefined as 'dark' | 'light' | undefined,
};

settings.retries ||= 3; // BAD: 0 becomes 3
settings.retries ??= 3; // GOOD: only null/undefined fallback

settings.label ||= 'Default'; // BAD: '' becomes 'Default'
settings.label ??= 'Default'; // GOOD: keeps intentional empty string

settings.theme ??= 'dark';
  • ||= assigns when the left side is falsy.
  • ??= assigns only when the left side is null or undefined.
  • For APIs and forms, ??= usually matches intent better.

2. Prefer satisfies over as for object literals

The satisfies operator checks that your object matches a target type while preserving the object inferred type.

This is safer than as, which can silence errors and widen useful literal information.

Shape validation without losing narrow types · ts

type Theme = {
  bg: `#${string}`;
  fg: `#${string}`;
};

const dark = {
  bg: '#000000',
  fg: '#ffffff',
} satisfies Theme;

// dark.bg is still the literal "#000000" (not widened to string)
const palette = [dark.bg, dark.fg];

const unsafe = {
  bg: '#000000',
  fg: '#ffffff',
} as Theme;
// "as" trusts you; "satisfies" verifies you
  • satisfies validates structure at compile time.
  • Literal values stay precise for better autocomplete and exhaustiveness checks.
  • as should be reserved for carefully justified edge cases.

3. Replace manual reduce with Object.groupBy

Grouping collections is one of the most common operations in frontend and backend code.

Object.groupBy removes boilerplate and makes intent explicit in one line.

Native grouping with clear output · ts

type User = {
  id: number;
  name: string;
  role: 'admin' | 'editor' | 'user';
};

const users: User[] = [
  { id: 1, name: 'Ana', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Eve', role: 'admin' },
  { id: 4, name: 'Max', role: 'editor' },
];

const usersByRole = Object.groupBy(users, (user) => user.role);

console.log(usersByRole.admin?.length); // 2
console.log(usersByRole.user?.length);  // 1
  • Great fit for dashboards, reporting, and partitioning lists by status.
  • Result keys map to arrays, often accessed with optional chaining.
  • For older runtimes, use a compatibility strategy or fallback reducer.

4. Know when const enum is useful and when it hurts

const enum removes runtime object lookups by inlining values at compile time. That is efficient for internal application code.

For shared libraries, const enum can create compatibility issues depending on consumer transpilation settings.

const enum vs enum emitted behavior · ts

const enum Direction {
  Up,
  Down,
  Left,
  Right,
}

const next = Direction.Left; // inlined number in emitted JS

enum HttpStatus {
  Ok = 200,
  NotFound = 404,
}

const status = HttpStatus.Ok; // runtime object lookup
  • Use const enum in app code where your build pipeline is controlled.
  • Prefer plain enum or union literals for distributed packages.
  • Never optimize enum usage blindly across a monorepo without checking consumers.

5. Use Array.at(-1) instead of manual index math

Array.at improves readability immediately, especially with negative indexing for last elements.

It also returns undefined safely for out-of-range access, which works well with strict null checks.

Readable indexing for arrays and strings · ts

const values = [10, 20, 30, 40];

const last = values.at(-1);        // 40
const secondLast = values.at(-2);  // 30

const first = values.at(0);        // 10
const maybe = values.at(999);      // undefined (safe out-of-bounds)

const slug = 'typescript';
const lastChar = slug.at(-1);      // "t"
  • Use at(-1) for the last item and at(-2) for the previous one.
  • Works with Array, String, and TypedArray.
  • Cleaner than arr[arr.length - 1] in most cases.

6. Advanced closure pattern: stateful async loader without classes

Closures are not just interview theory. They are one of the cleanest tools for encapsulating stateful behavior in production.

The pattern below keeps cache, in-flight deduplication, and request cancellation private while exposing a tiny public API.

Closure-based loader with cache, dedupe, and cancel · ts

type CacheEntry<T> = {
  value: T;
  expiresAt: number;
};

type Loader<T> = (key: string, signal: AbortSignal) => Promise<T>;

export function createSmartLoader<T>(loader: Loader<T>, ttlMs = 10_000) {
  const cache = new Map<string, CacheEntry<T>>();
  const inFlight = new Map<string, Promise<T>>();
  const controllers = new Map<string, AbortController>();

  async function get(key: string): Promise<T> {
    const now = Date.now();
    const cached = cache.get(key);
    if (cached && cached.expiresAt > now) return cached.value;

    const existing = inFlight.get(key);
    if (existing) return existing;

    const controller = new AbortController();
    controllers.set(key, controller);

    const request = loader(key, controller.signal)
      .then((value) => {
        cache.set(key, { value, expiresAt: Date.now() + ttlMs });
        return value;
      })
      .finally(() => {
        inFlight.delete(key);
        controllers.delete(key);
      });

    inFlight.set(key, request);
    return request;
  }

  function invalidate(key?: string) {
    if (key) cache.delete(key);
    else cache.clear();
  }

  function cancel(key: string) {
    controllers.get(key)?.abort();
  }

  return { get, invalidate, cancel };
}

const userLoader = createSmartLoader((id, signal) =>
  fetch(`/api/users/${id}`, { signal }).then((res) => res.json()),
);

await Promise.all([
  userLoader.get('42'),
  userLoader.get('42'), // deduped: same in-flight promise
]);
  • Encapsulation: internal maps stay private.
  • Performance: identical concurrent requests are deduplicated.
  • Control: cancel stale requests and invalidate stale cache entries.

Final take

If you apply only three of these this week, your code will already be safer and easier to maintain. If you apply all six, you also get a reusable pattern for building fast, predictable data flows in complex apps.

TypeScriptJavaScriptFrontendCode QualityPerformance