All posts
TypeScriptApr 20, 2026 · 3 min read

TypeScript Strict Mode Without Tears

A practical sequence for turning on strict mode in an existing codebase — which errors to fix first and which patterns make them disappear.

TypeScript Strict Mode Without Tears
On this page

Turning on "strict": true in an existing codebase produces a wall of red that makes most people turn it straight back off. That's a shame, because the wall is almost always the same five errors repeated, and each has a mechanical fix. Here's the sequence I used to take this site strict.

Fix the errors in frequency order

Run tsc --noEmit once and sort the output. You'll find the distribution is extremely top-heavy.

1. Implicit any on function parameters

The most common error by far, and the easiest:

// Before — error TS7006: Parameter 'tag' implicitly has an 'any' type
export const kebabCase = (str) => str.toLowerCase();
 
// After
export const kebabCase = (str: string) => str.toLowerCase();

Most of these are arrow functions in .map() and event handlers. Typing the source (the array, the props) usually fixes ten at once.

2. Untyped empty arrays

An empty array literal infers never[], which poisons everything pushed into it:

// Before — allTags grows via push, but its type is never[]
export const allTags = [];
 
// After — derive it, and the type comes for free
export const allTags = [...new Set(projects.flatMap((p) => p.tags))];

This exact pattern was a shipping bug on this site: an empty exported array that a forEach mutated later. Strict mode flagged it; deriving the value fixed both the type and the bug.

3. null checks on DOM lookups

document.getElementById returns HTMLElement | null, and strict mode makes you say what happens on null. Optional chaining is almost always the honest answer:

document.getElementById("learnmore")?.scrollIntoView({ behavior: "smooth" });

4. Children typed as any

Every layout-ish component needs the same fix:

export function Container({ children }: { children: React.ReactNode }) {
  return <div className="mx-auto max-w-screen-xl">{children}</div>;
}

5. Callbacks passed as untyped props

If a prop is a setter, type it as a function, not as any:

export const randomNumberText = (
  finalNum: string,
  setNumber: (value: string) => void,
) => {
  // ...
};

Don't use these escape hatches

A few "fixes" make the errors disappear while keeping the bugs:

  • as any — you've turned strict mode off for that expression, permanently
  • ! non-null assertions on DOM lookups — a runtime crash wearing a type annotation
  • // @ts-ignore — same, but it also hides future errors on that line

The one legitimate escape hatch is unknown + narrowing, which forces you to prove the type before using it.

The payoff is the next refactor

Strict mode's value isn't the errors it finds today — it's that every future rename, every dependency upgrade, every API change becomes a compiler-guided checklist instead of a prayer. When I later migrated this site's framework, the strict compiler caught every single call site that the new version broke. That migration would have shipped at least two runtime crashes without it.