Q36 of 38 · TypeScript

What techniques do you use to prevent `any` from spreading through a TypeScript codebase?

TypeScriptSeniortypescriptanytype-safetyeslintstrict-modecode-quality

Short answer

Short answer: `any` is contagious — it propagates through assignments and function returns. Prevent it with: `noImplicitAny` + `strict`, ESLint `@typescript-eslint/no-explicit-any`, using `unknown` at boundaries, replacing `any[]` with generics, and auditing `as` assertions. Track coverage with `typescript-strict-plugin` per file.

Detail

The insidious property of any is that it silently flows downstream — a single any in a utility function can infect every consumer's types.

Prevention strategies:

  1. noImplicitAny: The first line of defence. Forces explicit types where TypeScript cannot infer them. Enabled by strict: true.

  2. ESLint @typescript-eslint/no-explicit-any: Bans writing any explicitly in source. Pair with @typescript-eslint/no-unsafe-assignment (warns when assigning from any). These catch what noImplicitAny misses.

  3. Use unknown at boundaries: Functions that receive external data (JSON.parse, API calls, catch blocks) should use unknown not any. This forces narrowing at the point of use.

  4. Generic functions over typed-any arrays: processItems<T>(items: T[]): T[] instead of processItems(items: any[]): any[] preserves types through the function.

  5. Audit type assertions: as SomeType can mask any. Use satisfies or unknown + narrowing instead.

  6. Detect existing spread: typescript-strict-plugin (per-file strict configuration) or grep -r ': any' to audit. Track reduction over sprints as a metric.

// EXAMPLE

// ANTI-PATTERN: any at boundary, spreads everywhere
function parseConfig(raw: string): any { // any returns infect callers
  return JSON.parse(raw);
}
const config = parseConfig("{}");
config.nonExistent.deeply.nested; // no error — any swallows it

// BETTER: unknown + narrowing
interface Config { baseUrl: string; timeout: number; }
function parseConfig2(raw: string): Config {
  const val: unknown = JSON.parse(raw);
  if (
    typeof val === "object" && val !== null &&
    "baseUrl" in val && "timeout" in val
  ) {
    return val as Config; // one controlled assertion with prior narrowing
  }
  throw new Error("Invalid config shape");
}

// ESLint config — .eslintrc.json
{
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-unsafe-assignment": "warn",
    "@typescript-eslint/no-unsafe-return": "warn"
  }
}

// WHAT INTERVIEWERS LOOK FOR

The contagion property of `any` — that it spreads through assignments. Multiple prevention techniques: compiler flags, ESLint, `unknown` at boundaries, generics over `any[]`. The tracking/measurement mindset shows senior-level quality ownership.

// COMMON PITFALL

Adding ESLint's `no-explicit-any` without also adding `no-unsafe-assignment` — the latter catches cases where `any` flows in from external sources (JSON.parse, library return values) even without you typing `any` explicitly.