Skip to Content
Course content

122: Avoiding Overuse of any

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR from a developer who was clearly under a tight deadline. He was integrating a complex third-party analytics SDK that returned a deeply nested, unpredictable JSON object. To move past the compiler errors and get the feature shipped by Friday, he simply typed the entire response as any. It worked perfectly in staging. But three months later, the SDK provider pushed an update that changed a specific property from a string to an array of strings. Because the code used any, TypeScript stayed silent. We didn't find the bug until a production crash report hit my inbox at 2 AM, shouting about .toLowerCase() is not a function.

That's the danger of any. It isn't just a shortcut; it's an explicit instruction to the TypeScript compiler to stop helping you. When you use any, you're essentially telling the language, "I don't know what this is, and I don't want you to check it." In a small project, that's a minor risk. In a large codebase with multiple contributors, it's a landmine.

The Safety Gap Between any and unknown

If you find yourself reaching for any because you truly don't know what the data will look like—perhaps it's coming from an external API or a user-uploaded file—you should almost always use unknown instead. I think of any as "ignore this" and unknown as "I'm not sure yet."

The magic of unknown is that it forces you to perform a type check before you can actually use the value. You can't call a method on an unknown variable or access a property without proving to TypeScript that the variable is actually the type you think it is.

// This is dangerous. No errors here, but it might crash at runtime.
function processDataAny(data: any) {
    console.log(data.toUpperCase()); 
}

// This is safe. TypeScript won't let you call .toUpperCase() until you verify the type.
function processDataUnknown(data: unknown) {
    if (typeof data === "string") {
        console.log(data.toUpperCase()); // Now this is allowed
    }
}

Handling Dynamic Shapes with Record and Generics

Sometimes we use any because we're dealing with an object that has a dynamic set of keys, like a dictionary or a map. Instead of giving up and using any, use the Record<K, V> utility type. It allows you to define the type of the keys and the type of the values while still maintaining type safety for the object's structure.

For more complex scenarios where the output type depends on the input type, lean on generics. I've seen too many developers use any in a function because they want the function to be "flexible." But generics allow that flexibility without throwing away the type information. You're essentially creating a placeholder that gets filled in when the function is actually called.

// The "lazy" way: loses all type information
function wrapInArrayAny(item: any): any[] {
    return [item];
}

// The "pro" way: preserves the type for the caller
function wrapInArray<T>(item: T): T[] {
    return [item];
}

const numbers = wrapInArray(10); // TypeScript knows this is number[]
const strings = wrapInArray("Hello"); // TypeScript knows this is string[]

The goal isn't to achieve "perfect" types at the cost of your sanity, but to ensure that if something changes in your data layer, the compiler tells you exactly where your code is about to break. If you find yourself typing any, stop and ask: "Do I actually not know the type, or am I just trying to avoid the effort of defining it?"




📋 Practical Task

Refactoring the Legacy User Profile API

You have been handed a piece of legacy code that handles user profile updates. The previous developer used any everywhere to avoid dealing with the API's dynamic nature. Your task is to make this code type-safe without breaking the functionality.

Requirements:

  • Replace the any type in the UserProfile interface with a Record for the metadata field (keys are strings, values are strings).
  • Change the apiResponse parameter in the handleResponse function from any to unknown.
  • Implement a type guard or a type check inside handleResponse to ensure the response is an object and contains a status string before attempting to log it.
interface UserProfile {
    id: string;
    username: string;
    metadata: any; // TODO: Replace this
}

function handleResponse(apiResponse: any) { // TODO: Replace this
    // TODO: Add a check to ensure apiResponse is an object with a 'status' property
    console.log("API Status: " + apiResponse.status);
}

const user: UserProfile = {
    id: "u123",
    username: "dev_mentor",
    metadata: { theme: "dark", lang: "en" }
};

handleResponse({ status: "success", code: 200 });
Rating
0 0

There are no comments for now.

to be the first to leave a comment.