Tips

Small things, written down

One idea per entry, with the snippet and the thing that bites you. These are the notes I would otherwise lose — if a tip needs more than a couple of paragraphs it becomes a lesson instead.

20 tips · 7 categories

20 tips shown

Object.groupBy replaces your reduce

JavaScript
  • 24 Jul 2026

Grouping an array by a key was the single most common reason to reach for `reduce`. It is now built in, in both object and Map flavours. `Map.groupBy` is the one you usually want, because it preserves key types instead of coercing them to strings.

ts
const byStatus = Object.groupBy(orders, (o) => o.status);
// { open: [...], paid: [...] }

// keys stay as real values, not stringified
const byUser = Map.groupBy(orders, (o) => o.userId);

GotchaObject.groupBy returns a null-prototype object, so `hasOwnProperty` and friends are not on it.

SKIP LOCKED turns a table into a work queue

Postgres
  • 11 Jul 2026

You rarely need a queue broker for modest job volumes. `FOR UPDATE SKIP LOCKED` lets many workers pull disjoint batches from the same table without blocking each other — the second worker skips locked rows instead of waiting behind them.

sql
select * from job
where  status = 'pending' and run_at <= now()
order  by run_at
limit  20
for update skip locked;

GotchaWithout SKIP LOCKED every worker queues behind the first, and your concurrency silently becomes one.

AbortSignal.timeout for fetch deadlines

JavaScript
  • 30 Jun 2026

A `fetch` with no timeout can hang until the OS gives up, which on some platforms is measured in minutes. One static method gives you a deadline without wiring up a controller and a `setTimeout` by hand.

ts
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });

// combine a deadline with a user-triggered cancel
const signal = AbortSignal.any([
  AbortSignal.timeout(5000),
  userCancelController.signal,
]);

GotchaA timeout rejects with a TimeoutError DOMException, not the AbortError you get from manual cancellation. Check `err.name` if you branch on it.

:has() is the parent selector, and it ships everywhere

CSS
  • 18 Jun 2026

Styling a parent based on its children used to require JavaScript or a class toggle. `:has()` does it in CSS, and it works on sibling combinators too — which covers most of the cases people reached for a state class to solve.

css
/* a card that contains an image gets a different layout */
.card:has(img) { grid-template-columns: 120px 1fr; }

/* label reacts to its own input's state */
.field:has(input:invalid) { border-color: var(--red-500); }

/* only when NOT containing something */
.list:not(:has(li)) { display: none; }

satisfies keeps the narrow type

TypeScript
  • 2 Jun 2026

Annotating with `: Config` checks the object but widens every value to the declared type. `satisfies` checks it *and* keeps the literal types, so autocomplete on the keys still works.

ts
const routes = {
  home: '/',
  work: '/work',
} satisfies Record<string, `/${string}`>;

routes.home; // '/' — literal type preserved
// with ": Record<string, string>" this would just be string

INCLUDE gives you an index-only scan

Postgres
  • 21 May 2026

If a query filters on one column and selects one more, adding the second to the index payload lets Postgres answer entirely from the index and never touch the heap. `INCLUDE` adds it without making it part of the sort key.

sql
create index on orders (customer_id) include (total_minor);

-- now answered from the index alone
select total_minor from orders where customer_id = 42;

GotchaIndex-only scans still consult the visibility map. On a table with heavy churn and no recent VACUUM you will not get one.

The native dialog does focus trapping for you

HTML
  • 9 May 2026

A `<dialog>` opened with `showModal()` traps focus, handles Escape, renders in the top layer above any z-index, and exposes `::backdrop`. That is most of what a modal library sells you.

html
<dialog id="d">
  <form method="dialog">
    <button>Close</button>
  </form>
</dialog>

<script>d.showModal()</script>

<style>
  dialog::backdrop { background: rgb(0 0 0 / 0.6); }
</style>

GotchaUse `showModal()`, not `show()`. The non-modal version skips the focus trap and the backdrop entirely.

structuredClone for real deep copies

JavaScript
  • 27 Apr 2026

The `JSON.parse(JSON.stringify(x))` trick silently destroys Dates, Maps, Sets, BigInts and undefined, and throws on cycles. `structuredClone` handles all of them and is built into every current runtime.

ts
const copy = structuredClone(state);

// JSON round-trip would have turned this into a string
copy.createdAt instanceof Date; // true

GotchaIt cannot clone functions, DOM nodes, or class prototypes — you get a plain object back, not an instance.

content-visibility skips offscreen layout

CSS
  • 12 Apr 2026

On a long page, the browser lays out everything, including the parts nobody has scrolled to. `content-visibility: auto` lets it skip rendering work for offscreen subtrees. On list-heavy pages this is often the cheapest real win available.

css
.card {
  content-visibility: auto;
  /* reserve space so the scrollbar does not jump */
  contain-intrinsic-size: auto 320px;
}

GotchaWithout `contain-intrinsic-size` the scrollbar resizes as you scroll. Always pair them.

Sort slow queries by total time, not mean

Postgres
  • 30 Mar 2026

The slowest single query is usually a nightly report nobody is waiting on. The query actually hurting you is the 8ms one running forty thousand times an hour. Order by the product.

sql
select calls,
       round(mean_exec_time::numeric, 2) as mean_ms,
       round((mean_exec_time * calls / 1000)::numeric, 1) as total_s,
       query
from   pg_stat_statements
order  by mean_exec_time * calls desc
limit  10;

Brand your ids so you cannot swap them

TypeScript
  • 15 Mar 2026

Every id in your app is a string, so the compiler happily lets you pass a `userId` where an `orderId` was expected. A branded type makes that a compile error at zero runtime cost.

ts
type Brand<T, B> = T & { readonly __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;

declare function getOrder(id: OrderId): Promise<Order>;

const u = 'abc' as UserId;
getOrder(u); // ✗ Type 'UserId' is not assignable to 'OrderId'

inert removes a whole subtree from interaction

HTML
  • 28 Feb 2026

Hiding a drawer with opacity leaves every link inside it tabbable and readable by a screen reader. `inert` removes the entire subtree from the tab order and the accessibility tree in one attribute.

tsx
<main inert={menuOpen}>
  {/* nothing in here is focusable while the menu is open */}
</main>

GotchaReact needs a boolean here; older versions passed `inert="false"` as a string, which the browser reads as truthy.

fetchpriority on your LCP image

Performance
  • 14 Feb 2026

The browser guesses at image priority from layout, which it does not know until after CSS resolves. Telling it directly moves the hero image ahead of everything else in the queue and routinely takes a few hundred milliseconds off LCP.

html
<img src="/hero.avif" fetchpriority="high" alt="" width="1200" height="600">

<!-- and demote what you know is below the fold -->
<img src="/footer.avif" loading="lazy" fetchpriority="low" alt="">

GotchaNever put `loading="lazy"` on your LCP image. It delays the request until layout, which is exactly backwards.

RETURNING saves you a second query

Postgres
  • 29 Jan 2026

INSERT, UPDATE and DELETE all take a RETURNING clause. You get the affected rows back in the same round trip, with the values the database actually wrote — defaults, triggers, generated columns and all.

sql
insert into invoice (org_id, total_minor)
values ($1, $2)
returning id, number, created_at;

-- also works as a "did anything match?" check
delete from session where expires_at < now() returning id;

Cross-document view transitions in two lines

CSS
  • 16 Jan 2026

Same-origin navigations can cross-fade without a single-page-app router. Opt in from CSS and the browser handles the snapshot and the animation.

css
@view-transition { navigation: auto; }

/* name an element on both pages and it morphs between them */
.hero-image { view-transition-name: hero; }

GotchaSupport is still partial. It degrades to a normal navigation, so it is safe to ship — just do not build a layout that depends on it.

Error cause keeps the original stack

JavaScript
  • 19 Dec 2025

Wrapping an error to add context usually throws away the one thing you needed. The `cause` option chains them, and every modern logger and devtools will print the whole chain.

ts
try {
  await chargeCard(order);
} catch (err) {
  throw new Error(`Checkout failed for order ${order.id}`, { cause: err });
}

Generated columns keep derived data honest

Postgres
  • 4 Dec 2025

If a value is always computed from other columns, let the database compute it. It can never drift out of sync, and unlike a view it is indexable.

sql
alter table invoice
  add column total_minor bigint
  generated always as (subtotal_minor + tax_minor) stored;

create index on invoice (total_minor);

GotchaThe expression must be immutable — no `now()`, no subqueries, no other tables.

Reduce motion, do not remove it

Accessibility
  • 20 Nov 2025

The blanket `animation: none` under `prefers-reduced-motion` removes state-change feedback that some users rely on. The request is for less vestibular movement, not for a static page — so drop the travel and keep the fade.

css
.panel { transition: opacity 300ms, transform 300ms; }

@media (prefers-reduced-motion: reduce) {
  .panel {
    transition: opacity 300ms;  /* keep the cue */
    transform: none;            /* drop the movement */
  }
}

using for deterministic cleanup

TypeScript
  • 6 Nov 2025

Explicit resource management gives you scope-bound disposal — the same idea as Python's `with` or C#'s `using`. The resource is released when the block exits, including on a throw.

ts
async function migrate() {
  await using client = await pool.connect();
  await client.query('begin');
  // released automatically, even if this throws
}

GotchaNeeds a matching `[Symbol.dispose]` or `[Symbol.asyncDispose]` on the object, plus a recent TS target.

One aria-live region, announced once

Accessibility
  • 22 Oct 2025

A live region only announces when its contents change while it is already in the DOM. Rendering the region conditionally alongside the message means the announcement often never fires.

tsx
// ✗ region appears at the same time as the text — often silent
{error && <p role="alert">{error}</p>}

// ✓ region is always present; only the content changes
<p role="alert" aria-live="polite">{error ?? ''}</p>