precise-any-variants
Prefer More Precise Variants of any to Plain any
Overview
If you must use any, make it as specific as possible.
Plain any accepts everything. But any[], Record<string, any>, or () => any are narrower and still provide some type checking.
When to Use This Skill
- Forced to use any for some reason
- Writing functions that accept "anything"
- Dealing with truly dynamic data
- Migrating from JavaScript
The Iron Rule
any is a last resort.
Specific variants of any preserve partial type safety.
Remember:
any[]checks that it's an arrayRecord<string, any>checks that it's an object() => anychecks that it's a functionunknownis even safer than any variant
Detection: Over-broad any
function getLength(x: any) { // Too broad!
return x.length;
}
getLength(123); // No error, crashes at runtime
getLength(null); // No error, crashes at runtime
getLength([1,2,3]); // OK
Better: Specific any Variants
any[] for Arrays
function getLength(array: any[]) {
return array.length; // Type checked!
}
getLength([1, 2, 3]); // OK
getLength(/regex/);
// ~~~~~~~
// Argument of type 'RegExp' is not assignable to parameter of type 'any[]'
Benefits:
.lengthaccess is type-checked- Return type is
number, notany - Non-arrays are rejected
Record<string, any> for Objects
function hasKey(obj: Record<string, any>, key: string): boolean {
return key in obj;
}
hasKey({ a: 1 }, 'a'); // OK
hasKey(null, 'a');
// ~~~~
// Argument of type 'null' is not assignable to parameter of type 'Record<string, any>'
() => any for Functions
type Fn0 = () => any; // No params
type Fn1 = (arg: any) => any; // One param
type FnN = (...args: any[]) => any; // Any params
function callTwice(fn: FnN) {
fn();
fn();
}
callTwice(() => console.log('hi')); // OK
callTwice(123);
// ~~~
// Argument of type 'number' is not assignable to parameter of type '(...args: any[]) => any'
Use any[] for Rest Parameters
// any rest parameter: return type is any
const numArgsBad = (...args: any) => args.length;
// ^? (...args: any) => any
// any[] rest parameter: return type is number
const numArgsBetter = (...args: any[]) => args.length;
// ^? (...args: any[]) => number
The return type matters for downstream code!
Comparison of any Variants
| Type | Accepts | Rejects |
|---|---|---|
any |
Everything | Nothing |
any[] |
Arrays | Non-arrays |
Record<string, any> |
Objects | Primitives, null |
() => any |
Functions | Non-functions |
object |
Objects, arrays | Primitives, null |
unknown |
Everything | Everything (without check) |
Consider unknown Instead
unknown is even safer:
function process(data: unknown) {
// Must narrow before using
if (Array.isArray(data)) {
data.length; // OK, data is any[]
}
if (typeof data === 'object' && data !== null) {
// data is object
}
}
With unknown, you can't do anything without first checking the type.
Real-World Example: JSON Parsing
// Don't return any
function parseBad(json: string): any {
return JSON.parse(json);
}
// Better: return unknown
function parseGood(json: string): unknown {
return JSON.parse(json);
}
// Caller must narrow:
const data = parseGood('{"x": 1}');
if (typeof data === 'object' && data !== null && 'x' in data) {
console.log(data.x);
}
When any is Unavoidable
Sometimes you genuinely need any:
// Wrapping a library that uses any internally
function wrapLibrary<T>(input: T): T {
return (library as any).process(input);
}
// Type assertion in implementation
function merge<T>(a: Partial<T>, b: Partial<T>): T {
return { ...a, ...b } as any as T;
}
Even then, hide it inside functions with good type signatures (Item 45).
Pressure Resistance Protocol
1. "any Works"
Pressure: "Just use any and move on"
Response: any disables ALL type checking. Specific variants preserve some safety.
Action: Use the most specific variant that works.
2. "I Don't Know the Type"
Pressure: "The type is truly dynamic"
Response: unknown is safer for truly unknown types.
Action: Use unknown, then narrow before using.
Red Flags - STOP and Reconsider
anyas function parameter (use specific variant)anyas return type (prefer unknown)anyfor objects (use Record<string, any> or object)anyfor arrays (use any[])
Common Rationalizations (All Invalid)
| Excuse | Reality |
|---|---|
| "It's too dynamic to type" | unknown handles truly dynamic data |
| "Specific variants are verbose" | A few characters save runtime errors |
| "any is fine for internal code" | Internal code still has bugs |
Quick Reference
// DON'T: Plain any
function f(x: any): any { ... }
// DO: Specific variants
function getLength(arr: any[]): number { ... }
function getKeys(obj: Record<string, any>): string[] { ... }
function call(fn: (...args: any[]) => any): void { ... }
// BEST: unknown when possible
function process(data: unknown): void {
if (Array.isArray(data)) { ... }
}
The Bottom Line
If you must use any, make it specific.
any[], Record<string, any>, and () => any preserve some type checking while still allowing flexibility. But consider whether unknown is a safer choice.
Reference
Based on "Effective TypeScript" by Dan Vanderkam, Item 44: Prefer More Precise Variants of any to Plain any.