How 'Any' Ate My Codebase and All I Got Was a Passing Build
How 'Any' Ate My Codebase and All I Got Was a Passing Build
There is a moment in every TypeScript developer's career — quiet, unremarkable, devastating — when they type their first any. Maybe it's late on a Tuesday. Maybe the deadline is Thursday. Maybe the third-party library doesn't ship types and the DefinitelyTyped entry was last touched in 2019 by someone whose GitHub account has since gone dark. Whatever the circumstances, the moment arrives. The fingers move. The compiler stops complaining. And somewhere, very faintly, a door closes.
This is a meditation on that door. And what lives on the other side of it.
The Seduction of Silence
TypeScript's whole pitch — the reason you evangelized it to your team, the reason you wrote that internal wiki page titled Why We're Migrating Away From Plain JS (Please Read This) — is that it makes implicit problems explicit. The type system is supposed to be your conscience. A persistent, slightly annoying colleague who taps you on the shoulder and says, "Hey, are you sure that user.profile isn't undefined?"
any fires that colleague. Immediately. No severance.
When you annotate something as any, you're not telling the compiler "I know what this is." You're telling it "Stop asking questions." And the compiler, being a fundamentally obedient piece of software with no self-respect, shrugs and complies. The red squiggles vanish. The build passes. You ship.
For approximately six hours, this feels like a superpower.
The Spread
Here's what the TypeScript documentation won't tell you, mostly because it's too polite: any is ecologically aggressive. It doesn't stay where you put it. It spreads.
You add any to one function parameter because the incoming data structure is "complicated right now" and you'll "clean it up later." That function returns something. The thing that receives the return value now inherits the uncertainty. You annotate the receiver as any too, because what choice do you have? The thing that feeds into also becomes any. Within two weeks, you have a file that looks less like TypeScript and more like JavaScript with extra steps and a vague sense of shame.
This is not a metaphor. This is a documented phenomenon that TypeScript engineers call "type erosion," and that the rest of us call "what happened to this codebase, oh no."
The invasive species analogy holds up embarrassingly well. Kudzu vine was introduced in the American South as ground cover and erosion control. It seemed fine. It was contained. Then one morning everyone woke up and it had eaten three counties. any is kudzu. You cannot introduce it "a little."
The Five Stages of Type Abandonment
Denial: "It's just temporary. I'm coming back to fix this."
Bargaining: "Okay but if I add a comment that says // TODO: fix types that basically counts as fixing it."
Anger: "TypeScript is a tool. I am the master of this tool. The tool does not tell me what a UserResponse looks like."
Depression: You stop writing any explicitly and start just not writing type annotations at all. The compiler infers any on your behalf. You let it. You deserve each other.
Acceptance: You add "strict": false to your tsconfig.json, close the laptop, and go outside to stare at something that cannot disappoint you. A tree, maybe. Trees don't have types.
The Developer Who Once Had Opinions
The saddest part of the any spiral isn't the code. It's the person.
Remember when you had takes about type safety? Remember the Slack messages you sent? The code reviews where you left comments like "can we be more specific here?" and "this should probably be a discriminated union"? You had a whole thing about how TypeScript was only valuable if you committed to it fully, how half-measures were worse than nothing, how any was a code smell and a moral failing and a sign that a team had given up on itself.
That person is still in there. They are watching you write const data: any = await fetchStuff() and they are not saying anything because they are too tired, but they are watching.
The build passes. Standup is in twenty minutes. The watching continues.
// @ts-ignore: The Final Boss
For those who find any too verbose — too honest about their intentions — TypeScript offers // @ts-ignore. This is not a type. This is not a workaround. This is a suppression order. It is a developer telling the compiler, in writing, to please stop noticing things.
If any is a door closing, // @ts-ignore is boarding up the windows. If your codebase has more than three // @ts-ignore comments that aren't in test files or auto-generated code, it's time to have a conversation with your team. A gentle one. Maybe with snacks.
The 12-Step Recovery Program for any Survivors
For teams ready to crawl back toward strict: true, we offer the following program. It is not medically endorsed. It is not professionally endorsed. It is endorsed only by the ghost of the principled engineer you used to be.
- Admit you have a problem. Run
grep -r ": any" src/. Sit with the number. - Believe that stricter types can restore your sanity. This requires faith. That's okay.
- Enable
noImplicitAnyin a new branch. Do not look at the error count immediately. Pour a glass of water first. - Look at the error count. Breathe.
- Start with the leaves. Fix utility functions and pure helpers first. Do not start with the 800-line API response handler.
- Use
unknowninstead ofanyfor genuinely uncertain inputs.unknownforces you to narrow before use. It'sanywith accountability. - Make a list of every
anyyou introduced personally. You don't have to share this list. It's for you. - Stop adding new
anys. Even when it's hard. Especially when it's hard. - Delete
// @ts-ignorecomments one by one. Each one is a small victory. Treat it as such. - Review your
tsconfig.jsonand re-enable strict flags incrementally.strictNullChecksfirst. Then the rest. - Write a type for that third-party library yourself. Yes, you. A simple one. It doesn't have to be perfect.
- Help someone else on your team. When a colleague reaches for
anyat 11pm before a release, be there. Say "hey, what if we used a generic here?" Be the person you needed.
Recovery is not linear. There will be relapses. There will be a production incident at 2am where any seems like the only reasonable option and maybe it is the only reasonable option and you'll add it and fix it later and you will fix it later, probably, at some point, before the heat death of the universe.
But the goal is to keep the door open. To remember what it felt like when the compiler was your colleague instead of your adversary. To believe, against all evidence from your git history, that a well-typed codebase is possible and that you are the person who can build one.
The function that returns any promises nothing. You can do better than nothing.
Probably.