Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Avoid the any type — use unknown, generics, or type guards instead

Replace TypeScript's any type with unknown, proper generics, or narrowed type assertions to preserve type safety without sacrificing expressiveness.

Utilities
Quick take
Typical fix time 20 min
  • any disables all type checking for a value and cascades to callers
  • unknown is the safe alternative — it requires a type check before use
  • Use generics to express "I accept any type, but it stays consistent"
  • At API boundaries, validate with Zod rather than asserting with as Type
Why it matters: The any type is a local opt-out that becomes a global problem: a function returning any propagates unchecked types to every caller, silently undermining the type system across the entire codebase. Replacing any with unknown forces type narrowing at the point of use, turning latent runtime errors into compile-time failures where they are cheapest to fix.

Rule Details

any (opens in new tab) is TypeScript's escape hatch — it tells the compiler to stop type-checking a value entirely. Every property access, method call, and assignment on an any value is silently accepted, and unknown (opens in new tab) exists precisely so you do not have to make that tradeoff.

Code Example

// ❌ any defeats type checking silently
function parseConfig(raw: any) {
  return raw.settings.timeout // no error if 'settings' doesn't exist
}
 
// ❌ any spreads to callers
const config = parseConfig(JSON.parse(text)) // config is also 'any'
const ms = config.timeout * 1000             // no error even if timeout is undefined

Why It Matters

The any type is a local opt-out that becomes a global problem: a function returning any propagates unchecked types to every caller, silently undermining the type system across the entire codebase. Replacing any with unknown forces type narrowing at the point of use, turning latent runtime errors into compile-time failures where they are cheapest to fix.

unknown — the Safe Alternative

unknown is the type-safe counterpart to any. You can assign anything to unknown, but you cannot use the value without first narrowing its type.

// ✅ unknown forces a type check before use
function parseConfig(raw: unknown): AppConfig {
  if (
    typeof raw === 'object' &&
    raw !== null &&
    'settings' in raw &&
    typeof (raw as { settings: unknown }).settings === 'object'
  ) {
    // Now TypeScript knows enough to work with 'settings'
  }
  throw new Error('Invalid config shape')
}
 
// ✅ Simpler: use Zod for external data (see runtime-validation rule)
import { z } from 'zod'
 
const ConfigSchema = z.object({
  settings: z.object({
    timeout: z.number(),
  }),
})
 
function parseConfig(raw: unknown): AppConfig {
  return ConfigSchema.parse(raw) // throws if shape is wrong, returns typed value
}

Generics — Type Consistency Without any

When you need a function that accepts multiple types, generics express "whatever type you pass in, I preserve it" — something any cannot do.

// ❌ any loses the relationship between input and output types
function first(arr: any[]): any {
  return arr[0]
}
const x = first([1, 2, 3])   // x is 'any', not 'number'
const y = first(['a', 'b'])  // y is 'any', not 'string'
 
// ✅ Generic preserves the element type
function first<T>(arr: T[]): T | undefined {
  return arr[0]
}
const x = first([1, 2, 3])   // x is 'number | undefined'
const y = first(['a', 'b'])  // y is 'string | undefined'

Type Assertions — When They Are Acceptable

A type assertion (as Type) is appropriate when you have verified the type through a mechanism TypeScript cannot see (e.g., a DOM API or a third-party library with weak types). It is dangerous when used as a shortcut to silence an error.

// ✅ Acceptable assertion — we checked the element exists and know its type
const canvas = document.getElementById('main-canvas')
if (!(canvas instanceof HTMLCanvasElement)) {
  throw new Error('Expected a canvas element')
}
const ctx = canvas.getContext('2d') // canvas: HTMLCanvasElement — safe
 
// ✅ Acceptable — casting from a loosely-typed SDK response
const response = await stripe.charges.create(params)
const charge = response as Stripe.Charge // Stripe SDK uses loose types internally
 
// ❌ Dangerous — silencing a type error without verification
const user = rawData as User     // rawData could be anything; this hides the problem
user.profile.avatar.url          // crashes if rawData doesn't match User

Type Guards — Reusable Narrowing

// Reusable type guard function
function isApiError(value: unknown): value is { message: string; code: number } {
  return (
    typeof value === 'object' &&
    value !== null &&
    'message' in value &&
    'code' in value &&
    typeof (value as Record<string, unknown>).message === 'string' &&
    typeof (value as Record<string, unknown>).code === 'number'
  )
}
 
// Usage — TypeScript narrows inside the if block
try {
  await fetchData()
} catch (error: unknown) {
  if (isApiError(error)) {
    console.error(`API error ${error.code}: ${error.message}`) // fully typed
  }
}

ESLint Configuration

The @typescript-eslint/no-explicit-any (opens in new tab) rule is the practical way to keep this standard from drifting during code review.

// .eslintrc.json / eslint.config.js
{
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-unsafe-assignment": "error",
    "@typescript-eslint/no-unsafe-call": "error",
    "@typescript-eslint/no-unsafe-member-access": "error",
    "@typescript-eslint/no-unsafe-return": "error"
  }
}
any in third-party library types

Sometimes a dependency's type definitions use any internally. When your code infers any from a library call, the fix is either to add a local type assertion with a guard, use the library's correct type (check @types/library), or wrap the call in a Zod schema. Do not widen your own code to any just because a dependency uses it.

Zod at the API Boundary

The cleanest strategy for external data is to validate at the entry point and never let unknown or any past the boundary:

import { z } from 'zod'
 
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
  email: z.string().email(),
  role: z.enum(['admin', 'user', 'viewer']),
  createdAt: z.coerce.date(),
})
 
// TypeScript type derived from the schema — single source of truth
type User = z.infer<typeof UserSchema>
 
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`)
  const json: unknown = await response.json()
  return UserSchema.parse(json) // throws ZodError if shape is wrong
}

Verification

  1. Run pnpm exec eslint --rule '{"@typescript-eslint/no-explicit-any": "error"}' src/ and confirm zero violations, or check that the rule is already in your ESLint config with "error" severity.
  2. Search the codebase for : any and as any — every remaining instance should either be enforced by @typescript-eslint/no-explicit-any (opens in new tab) or have a comment explaining why it is intentional and cannot be replaced.
  3. Enable "noImplicitAny": true in tsconfig.json (included in "strict": true) and confirm tsc --noEmit produces zero errors.
  4. Review functions that receive data from fetch, localStorage, or URL parameters — each should accept unknown and validate before use, not accept or return any.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Scan this TypeScript file for uses of the any type (explicit annotations, implicit any from missing types, and type assertions to any). Report each location and suggest a safer alternative.

Fix

Auto-fix issues

Replace the any types in this code with unknown (for values of unknown shape), appropriate generics (for type-consistent operations), or Zod validation (for external data). Show the narrowing or generic constraint needed at each usage site.

Explain

Learn more

Explain why the any type undermines TypeScript's guarantees, how unknown differs from any, and when a type assertion (as Type) is acceptable versus dangerous.

Review

Code review

Review all type annotations, function signatures, and external data handling in this file. Flag every explicit or implicit any, any type assertion that lacks a preceding type guard, and any return types inferred as any from untyped dependencies.

Sources

References used to support the guidance in this rule.

Rules that often go hand-in-hand with this one.

Enable TypeScript strict mode in tsconfig.json

Enable "strict": true in tsconfig.json to activate the full suite of TypeScript type-checking flags and catch the most common runtime bugs at compile time.

JavaScript
Validate external data at runtime with a schema library

Use Zod or Valibot to validate data from API responses, form inputs, localStorage, and environment variables — TypeScript types are erased at runtime and cannot protect against unexpected shapes.

JavaScript
Lint JavaScript code

JavaScript code is linted with ESLint to detect errors and enforce coding standards.

JavaScript
Parse JSON safely with error handling

Always wrap JSON.parse() in try/catch and validate the parsed structure before use, as invalid JSON or unexpected data shapes cause runtime errors.

JavaScript

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385