Congratulations! You’ve adopted TypeScript. Now you’re writing JavaScript with commitment issues.
Let me paint you a picture: your team had The Meeting. Someone said ” type safety ” while gesturing vaguely at a stack trace. Someone else mentioned ” developer experience ” after their third coffee. Everyone nodded like they understood what that meant. You migrated your codebase, spent two days fixing config files, added typescript to your dependencies, and updated your LinkedIn.
Then you wrote this masterpiece:
const data: any = await fetchUserData();
Chef’s kiss. Beautiful. You’ve taken a language designed to catch bugs at compile time and told it “nah, surprise me at runtime.” It’s like buying a seatbelt and wearing it as a scarf.
TypeScript’s whole thing is preventing undefined is not a function from ruining your Thursday afternoon. But any is the ” I trust the universe ” button—and you’re mashing it like it owes you money. The compiler isn’t fooled. Your IDE isn’t fooled. And that senior dev who’s reviewed fifteen PRs with any in them today? They’re not fooled either. They’re just tired.
“I’ll Just Use ‘any’ For Now”
Ah yes, the most enduring lie in software development. Right up there with ” I’ll add tests later” and ” this technical debt is temporary. “
The deadline is breathing down your neck. Product wants it shipped yesterday. The API response looks like someone nested objects while having an existential crisis. You’ll come back and “do it properly” after launch.
// Written during "crunch time" (6 months ago)
async function getUserProfile(id: string): Promise<any> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// Meanwhile, in your component
const user = await getUserProfile("123");
console.log(user.profile.settings.theme.darkMode); // 🤞
Narrator: They did not come back and fix it.
That code has survived three refactors, two new team members, and a deployment pipeline upgrade. Nobody knows what shape that response is anymore. Nobody dares to ask. It’s Schrödinger’s API response—simultaneously every shape and no shape until runtime collapses the wave function (into an error).
What actually happens: The backend team renames profile to userProfile during their own refactor. They update their API docs (which nobody reads). TypeScript doesn’t warn you because you told it not to. Your tests pass because they mock the old structure. A user clicks “Settings” on a Tuesday morning, gets a white screen of death, and your Slack explodes with PagerDuty notifications.
You spend forty-five minutes spelunking through production logs, eventually finding Cannot read property 'settings' of undefined buried under seventeen stack frames. You fix it by adding optional chaining like a bandaid on a bullet wound: user?.profile?.settings?.theme?.darkMode. Your code now looks like a suspicious person checking if they’re being followed.
Your IDE, meanwhile, has completely given up on you. No autocomplete. No “go to definition.” No helpful inline docs. It’s showing you the same level of support it shows for a text file. You’re writing JavaScript with extra ceremony and occasional red squiggles. Congratulations.
The any Starter Pack
Let me guess your codebase. Don’t worry, I’ve seen this episode before:
The ” I’ll Deal With It Later “
const data: any = JSON.parse(localStorage.getItem('user') || '{}');
Translation: “There’s probably a user object in localStorage. Or maybe null. Or maybe corrupted JSON from that bug three releases ago. Let’s find out at runtime like our ancestors did!”
The API Pessimist
axios.get<any>('/api/products').then((response: any) => {
// ✨ manifestation-based development ✨
console.log(response.data.items);
});
Translation: “The backend team changes this API every Tuesday. I’m not falling for their tricks again.”
Plot twist: you’re falling for different tricks now—the ones where your code breaks silently.
The React White Flag
interface Props {
data: any;
onUpdate: (val: any) => void;
config?: any;
className?: string; // at least we typed one thing!
}
Translation: “Thinking about props makes my head hurt.” Your component is now a mystery box. Using it requires reading the implementation and saying a little prayer. Good luck to whoever has to maintain this.
The Function That Could Be Anything
function processData(...args: any[]): any {
// trust fall
return args.reduce((acc: any, curr: any) => {
return acc + curr;
}, 0);
}
Translation: ” Functions can be hard. ” This signature tells you nothing. Takes anything. Returns anything. Could it throw? Who knows! What arguments does it actually need? Read the code and squint really hard. This function is a riddle wrapped in a mystery inside an any.
Each any is a surrender flag. A monument to the exact moment you decided that type safety was someone else’s problem. Your codebase is littered with them like used tissues.
“But The Library Doesn’t Have Types!”
Alright, sometimes this excuse has merit. You’re using super-obscure-carousel that some developer abandoned in 2016 to go find themselves in Tibet. No TypeScript support. No types. No hope.
But have you checked DefinitelyTyped? The community has written type definitions for over 9,000 packages (yes, including that weird date picker you’re about to install). They’ve probably typed your obscure carousel. They type things as a hobby. It’s beautiful and slightly concerning.
npm install --save-dev @types/that-janky-library
Boom. Typed. That took longer to read than to execute.
” But what if it’s really obscure? ” Fine. The library is hyper-niche-sdk and there are zero types anywhere because its only users are you and a person in Romania who hasn’t updated their dependencies since 2019.
You still have options that don’t involve any:
Option 1: Use unknown (the adult in the room)
const result: unknown = hyperNicheSdk.doWeirdThing();
// TypeScript is your mom asking "did you check?"
if (typeof result === 'object' && result !== null && 'status' in result) {
console.log(result.status); // ✅ TypeScript: "okay, proceed"
}
Option 2: Write your own damn types
Create types/hyper-niche-sdk.d.ts:
declare module 'hyper-niche-sdk' {
export interface WeirdThingConfig {
apiKey: string;
timeout?: number;
shouldBreak?: boolean; // accurate
}
export function doWeirdThing(config: WeirdThingConfig): Promise<{
status: 'success' | 'error' | 'who_knows';
data?: string;
}>;
}
Look at that. You just typed a library. Took about two minutes. Now your whole team gets autocomplete and your code is self-documenting. You’re a goddamn hero. Put it on your resume.
Even a rough type definition is infinitely better than any. It’s documentation. It’s a contract. It’s you being nice to your future self. any is you leaving a note that says “good luck lol.”
The Slippery Slope (Or: How I Learned to Stop Worrying and Lose All Type Safety)
Here’s the thing about any that nobody tells you: it’s viral. It spreads like gossip at a startup.
// Patient zero
const apiData: any = await fetchData();
// TypeScript's inference is now having an existential crisis
const userName = apiData.user.name; // userName: any (cool cool cool)
const userId = apiData.user.id; // userId: any (everything is fine)
// Which means functions that use these values also get infected
function displayUser(name: any, id: any) {
// TypeScript has left the chat
console.log(`User: ${name}`);
}
// And the infection spreads
const users = [userName, "Alice", "Bob"]; // users: any[]
const processed = users.map(u => u.toUpperCase()); // processed: any[]
// Eventually, your entire module is type-less
export function processAllUsers(data: any): any {
// we've come full circle
return data;
}
One any at the entry point metastasizes through your entire module. Type inference—the thing that makes TypeScript actually pleasant to use—collapses like a house of cards. Every value downstream becomes any. You’ve created a type-checking black hole that consumes all safety guarantees within its event horizon.
This poisons team culture too. New developer joins, runs the linter, sees 847 instances of any, and thinks “oh, this is just how we do TypeScript here.” They copy the pattern because that’s what you do when you’re new. Code reviews stop mentioning it because reviewing 847 things is someone’s full-time job. Your TypeScript codebase evolves into JavaScript: The Verbose Years.
Six months later, someone suggests removing TypeScript entirely. “We’re not getting any ROI on it,” they say during retro. And they’re absolutely right—you paid for a safety net and then spent months carefully cutting holes in it.
The Redemption Arc (Or: How to Stop Being Part of the Problem)
Good news! TypeScript has features specifically designed for every situation where you’re tempted to type something as any. Let’s rehab your code. First step is admitting you have a problem.
unknown: The friend who keeps you honest
// Your code now:
function parseJSON(input: string): any {
return JSON.parse(input);
}
// Imagine this instead:
function parseJSON(input: string): unknown {
return JSON.parse(input);
}
// Now TypeScript won't let you YOLO it:
const result = parseJSON('{"name": "Alice"}');
// ❌ TypeScript: "absolutely not"
console.log(result.name);
// ✅ TypeScript: "okay, you've done the work"
if (
typeof result === 'object' &&
result !== null &&
'name' in result &&
typeof result.name === 'string'
) {
console.log(result.name.toUpperCase());
}
Yes, that’s more verbose. You know what else is verbose? The postmortem document explaining how undefined.toUpperCase() took down production. unknown forces you to prove you know what you’re doing. That’s not bureaucracy—that’s adulting.
Generics: Not actually scary
// Your function:
function getFirst(arr: any[]): any {
return arr[0];
}
// Same function, but now it works:
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
// Watch the magic:
const nums = [1, 2, 3];
const first = getFirst(nums); // first: number | undefined
const names = ["Alice", "Bob"];
const firstName = getFirst(names); // firstName: string | undefined
Congratulations, you just wrote a generic function. Was that traumatic? Do you need a moment? You can now use this with anything—numbers, strings, objects, hopes, dreams—and TypeScript tracks it all. This is the good stuff.
Utility types for when you’re feeling fancy
interface User {
id: string;
name: string;
email: string;
age: number;
password: string;
isAdmin: boolean;
}
// Everything optional (for updates):
type UserUpdate = Partial<User>;
// Just the public stuff:
type UserProfile = Pick<User, 'id' | 'name' | 'email'>;
// Everything except sensitive stuff:
type SafeUser = Omit<User, 'password' | 'isAdmin'>;
// Make it readonly (for paranoid people like me):
type ImmutableUser = Readonly<User>;
These are built into TypeScript. No packages. No config. No excuses. Stop typing things as any because “the shape is complicated.” The shape is fine. You’re just being dramatic.
Type guards: Teaching TypeScript your validation logic
interface Dog {
type: 'dog';
bark: () => void;
goodBoy: true;
}
interface Cat {
type: 'cat';
meow: () => void;
respectsYou: false;
}
// Write your validation once:
function isDog(animal: Dog | Cat): animal is Dog {
return animal.type === 'dog';
}
// Use it everywhere:
function makeSound(animal: Dog | Cat) {
if (isDog(animal)) {
animal.bark(); // TypeScript knows: it's a Dog
console.log('Good boy!');
} else {
animal.meow(); // TypeScript knows: it's a Cat
console.log('You tolerate me.');
}
}
You write the check once. TypeScript remembers forever. This is power. This is what you signed up for. Not any and prayers.
“But TypeScript Is Slowing Me Down!”
Let me address this directly: no it isn’t. Your unfamiliarity with TypeScript is slowing you down. TypeScript, properly used, is a productivity multiplier.
“I’m faster in JavaScript!” Okay. How many times this month have you:
- Debugged
undefined is not a function? - Spent ten minutes figuring out what properties an object has?
- Broken something in one file because you changed something in another?
- Shipped a bug because you misremembered an API response shape?
TypeScript catches all of that in seconds. Before you run the code. Before it hits staging. Before some user in production discovers your typo. The time you spend writing types is time you would have spent debugging, except now you’re doing it proactively while you still remember what the code does.
Plus—and I cannot stress this enough—autocomplete is a superpower. Type a variable name, press dot, see every available property and method. With documentation. You’re not constantly alt-tabbing to MDN or that file where you defined the interface six months ago. You just… code.
And refactoring? Try renaming a property in a JavaScript codebase with 50 files. You grep. You pray. You miss two usages. You find out three days later when someone reports a bug. In TypeScript? Right-click, rename, done. The compiler finds every usage. All of them. Even the ones in that file nobody’s opened since 2022.
“But I’ll prototype without types and add them later!” Sweet summer child. No you won’t. That’s like saying you’ll floss after you get cavities. Type as you go, even roughly, and you’ll save time, sanity, and that feeling you get at 3 AM when you realize you’ve been chasing a typo for forty-five minutes.
Look, We’ve All Been There
I’m not claiming I’ve never written any. I’ve written plenty. Deadlines happen. Legacy codebases happen. Sometimes you’re integrating with a third-party API that was designed by someone who clearly hated joy, and typing it properly would take four hours you don’t have.
We’ve all been there. I’ve been there. I’ll probably be there again next Tuesday.
But here’s the thing: if you’re going to use TypeScript, use TypeScript. Don’t sprinkle any everywhere like it’s parmesan cheese and then wonder why you’re not getting the benefits everyone on Twitter promised you. TypeScript’s type system isn’t a punishment handed down from the ivory tower—it’s a tool. A really, really good tool that you’re using as a paperweight.
Here’s your homework (yes, I’m assigning homework, deal with it):
Open your codebase right now. Don’t think about it, just do it. Find one any. Literally any any. Look at what it’s typing. Ask yourself:
- Can I use
unknowninstead? - Can I write a quick interface?
- Can I use a generic here?
- Can I add a type guard?
- Do I actually know what this is supposed to be?
Fix that one any this week. Just one. Then find another next week. You don’t have to fix them all at once. Rome wasn’t built in a day, and your codebase won’t be un-any’d in one either.
And if your codebase is so saturated with any that you don’t even know where to start—if opening a file feels like opening a time capsule of bad decisions—maybe it’s time to have a team conversation. Are we using TypeScript? Or are we LARPing as people who use TypeScript?
Because right now, your types aren’t fooling anyone. Not the compiler. Not your coworkers. Not that new hire who’s been here three days and is already questioning their life choices.
Now go forth and type things properly. Your future self is begging you. So is your IDE. We’re all rooting for you.
P.S. – If you fix an any today, you’re legally allowed to be smug about it for 24 hours. That’s just science.