How to Avoid :any and Define Better Types in TypeScript?
TypeScript was created to bring type safety and predictability to JavaScript applications. But when developers overuse any, they strip away the core benefits TypeScript provides.
In this article, we’ll explore:
What is any in TypeScript?
The any type in TypeScript disables type checking for a variable or parameter. It tells the compiler:
"Trust me. I know what I’m doing."
let data: any;
data = 5;
data = "Hello";
data = { name: "John" };
✔️ any accepts all types
❌ TypeScript will not validate what you do with it
🚫 Why You Should Avoid "any"
1. ❌ It Removes Type Safety
When you use any, you lose TypeScript’s biggest advantage: compile-time error checking.
let user: any = "John";
console.log(user.toFixed(2)); // Runtime error! Strings don’t have toFixed
The compiler won't warn you, but the app will crash at runtime.
2. ❌ It Increases Runtime Bugs
You might think your code is fine, but "any" allows silent errors that surface only in production.
3. ❌ Poor Code Readability
"any" hides the shape and purpose of your data, making it harder for other developers (or future-you) to understand how to use it properly.
4. ❌ Misses IDE Autocomplete and IntelliSense
When you use "any", modern editors like VS Code can’t suggest properties or methods, which reduces developer productivity.
✅ Safer Alternatives to any
1. unknown (Safer Substitute)
unknown forces you to explicitly check types before using the value.
let response: unknown;
if (typeof response === 'string') {
console.log(response.toUpperCase());
}
✔️ Safer than any
✔️ Forces you to validate
2. Strongly Typed Interfaces
Use interfaces or type aliases to describe your data structure.
interface User {
id: number;
name: string;
}
let user: User = { id: 1, name: 'John' };
✔️ Enables autocomplete
✔️ Catches errors early
✔️ Improves code documentation
3. Generics
Generics help you write flexible but still type-safe functions.
function getFirstItem<T>(items: T[]): T {
return items[0];
}
getFirstItem<number>([1, 2, 3]); // TypeScript knows it returns number
✔️ Avoids hardcoding types
✔️ Maintains safety
✅ 2. Use :unknown Instead of :any for Dynamic Data
✔️ When receiving JSON, API responses, or user inputs.
let apiData: unknown = fetchData();
✅ 3. Define Interfaces or Type Aliases for Complex Objects
✔️ Makes your code self-documenting
✔️ Helps with readability and maintainability
type ApiResponse = {
status: string;
data: User[];
};
✅ 4. Use Generics for Reusable Functions
✔️ Type-safe utility functions
✔️ Reduces code duplication
function getFirstItem<T>(arr: T[]): T {
return arr[0];
}
// Example with numbers
const numberItem = getFirstItem<number>([1, 2, 3]); // numberItem: number
// Example with strings
const stringItem = getFirstItem<string>(["a", "b", "c"]); // stringItem: string
// Example with objects
interface User {
id: number;
name: string;
}
const userItem = getFirstItem<User>([
{ id: 1, name: "John" },
{ id: 2, name: "Jane" }
]); // userItem: User
✅ 5. Avoid Implicit any
✔️ Enable noImplicitAny in your tsconfig.json to catch missing types.
{
"compilerOptions": {
"noImplicitAny": true
}
}
✅ 6. Use the TypeScript Record Utility for Dynamic Keys
✔️ Type-safe dynamic object structures
const userPermissions: Record<string, boolean> = {
canEdit: true,
canDelete: false
};
✅ 7. Gradually Add Types in Legacy Projects
✔️ Use ts-ignore temporarily
✔️ Replace any progressively with specific types
✅ 8. Prefer Third-Party Type Definitions
✔️ Use @types/* packages to type popular libraries
npm install --save-dev @types/lodash
✅ 9. Write Clear Function Signatures
✔️ Always define parameter and return types
function calculateTotal(price: number, tax: number): number {
return price + tax;
}
✅ 10. Use Type Inference But Be Explicit When Needed
✔️ TypeScript can infer simple variables but don’t over-rely on it for complex structures.
const age = 30; // inferred as number