Unconventional Ways to Cast in Typescript
Posted2 months agoActive2 months ago
wolfgirl.devTechstory
heatedmixed
Debate
80/100
TypescriptType CastingProgramming Best Practices
Key topics
Typescript
Type Casting
Programming Best Practices
The article discusses unconventional ways to cast types in TypeScript, sparking a debate among commenters about the legitimacy and usefulness of such approaches.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
4h
Peak period
23
6-12h
Avg / period
6.9
Comment distribution55 data points
Loading chart...
Based on 55 loaded comments
Key moments
- 01Story posted
Oct 23, 2025 at 9:25 AM EDT
2 months ago
Step 01 - 02First comment
Oct 23, 2025 at 1:25 PM EDT
4h after posting
Step 02 - 03Peak activity
23 comments in 6-12h
Hottest window of the conversation
Step 03 - 04Latest activity
Oct 25, 2025 at 12:27 PM EDT
2 months ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
ID: 45681547Type: storyLast synced: 11/20/2025, 3:22:58 PM
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
But mostly it just gave me a chuckle. I tried it because it seemed logical, but I didn't really think it was going to work until it did..
It really should be called "vaguely typed script"
If it were this strict out-of-the-box, it probably would have been hated since most devs don't really want to deal with static typing.
https://typescript-eslint.io/getting-started/typed-linting
I expect this comment is from a person who has not used strongly typed languages extensively. Is that true?
When "1" == 1, and "1" can cross a wire and be assigned to a number, when there are no ints...
Perhaps I have not given Typescript enough rope, I only spent eighteen months immersed
It is not shit, but it is very outdated IMO.
I have been known to be wrong, I admit, but all these "unconventional " casts, together with the standard ones, are exactly what frustrated me
> If you're holding it right, these things don't come up, and your code genuinely is much much safer than if you used raw Javascript.
Just wanted to highlight this point I feel needs to be underscored
If you have complex types, it's sometimes the easiest way to do what you want, and it's perfectly safe as long as you are 100% sure that the types are compatible.
For example, where you have a fluent-style API where each method modifies the types it's unavoidable to end up using that kind of cast
That was funny to read
To give you an example from a popular open source ts-heavy project:
https://github.com/elysiajs/elysia/blob/94abb3c95e53e2a77078...
The `return this as any` there, which effectively casts it to the same type this had, but with the added get route is perfectly safe, it works, and will never be a problem by itself.
If you were going to rely on that anyway, why not just use JavaScript as is and avoid the boilerplate from typescript
I ever used Typescript because I hoped it would catch some bugs that I didn't (and it did). I never expect it to be 100%. I can't even imagine 100% of what.
It's not like using js at all, not that I think there's anything wrong with it, if that's your jam.
Typescript is "good enough" at it's job. That's a great reason to use it.
Tbh I'm not paid by this company enough to spend time breaking down the correct solution but it would involve validating and discriminating the result of `this.add`.
This code is NOT type-safe and will be harder to maintain. Someone will make a change in the future that changes the possible results of `this.add` and TypeScript will not be able to warn you about the consequences of that change.
There are certainly ways to guard against that, but most of them involve some amount of accepting that the type checker produces errors for a reason.
Bear in mind, most changes that could cause issues will still be caught by the type checker in whatever object you're casting to. Obviously it should not be overused where not needed, but it's almost always used in fluent apis because there's no better way (that I know of, at least)
Got an example?
This is not the easiest to follow code, but it's very similar to what you'd find in any fluent web router, the idea is that you have say an App class, which has a Routes generic, then on every route you add you compose the return types by returning this as App<Routes & NewRoute>, the thing is in the most simple cases you can probably do this cast directly and it will be fine, but as you add more features (things like extensibility with plugins, ability to merge to other app routes, etc..) you might eventually run into limitations of the type system that require a escape hatch like "as unknown" or "as any"
It's not the only case in which you might use it, but I think Elysia is a great example as it does some really interesting things with the type system to provide great DX
Here's an example: https://tsplay.dev/w8y9PN
[1]: https://www.typescriptlang.org/docs/handbook/release-notes/t...
[2]: Doing this isn't safe anyway because it mutates an object in a type-relevant way while there may other variables referring to it (the safe thing to do is return a new `Elysia` instance from `get`), but that's beside the point.
I think a more concrete example would be necessary but I highly doubt there isn't a more elegant solution using unions and discriminators
> it's sometimes the easiest way to do what you want
This intention is exactly what leads to unmaintainable typescript codebases imo. Thinking you "know better than TypeScript". TS thinks what it thinks for a reason. Usually that reason is past decisions you made
I also don't think it can be perfectly safe. Use a validator if you want it to be perfectly safe
Mostly true.
But most system also ingest external data -- that data truly is "unknown" until you parse/validate it. Once you do parse/validate it, you're going to want it to have the correct type.
You shouldn't "never" use. You should only use it in limited, constrained ways, and always with other controls that are sufficient to ensure it's correct.
Even then, a solid validation/parsing library like Zod is a better choice than casting, it is much less error prone at ensuring you have the right type applied.
Beyond that boundary, casting with `as unknown` is a massive code smell and often introduces any further use of that data to invalid type errors. I'll refer to this as "force casting", because simply casting "x as T" with sufficient strictness configured will still fail type checking if x is not compatible with T, and that can be fine for certain cases like reducing a complex type or giving an inferred parser type a form that fits your domain types.
Developers that force cast too readily can end up creating hundreds or even thousands of type errors in the codebase over time, and they are pretty quickly revealed once you peel away the casting. The same is true when starting from an `any`, where every reference thereon is equivalent to a force cast.
There can be some complex typing scenarios that do require it, but even then a good type guard works better even if certain cases can be semantically equivalent, you can write tests around guards as they're just functions, ensuring some logical correctness to the typing.
The simplest but maybe odd way to judge whether a casting is used the wrong way is to ask "are you lying to the type system?". If so, how can you better portray that type correctly (validation that determines a correct type for you (e g. Zod) or type guards).
(Personally, I'm not convinced zod itself is the way to go -- I'm suspicious of its lack of stability. The last thing I want to do is redevelop the boundaries of my system in a few years because zod 4 has been killed off in favor of zod 5, and then again in a few more years for zod 6, etc.)
Absolutely. And there are tools like Zod or e2e type safety solutions to solve this exact problem. And otherwise that data will come in as `any` or already as `unknown` so there will never be a use case for `as unknown as B`. This only happens when the data has already been assigned a type (here, the type is A). Now you're working on recasting it to fix your mistake instead of fixing the core issue of the incorrectly typed data.
The `A as unknown as B` workaround is only relevant when TypeScript believes A and B are so drastically different that they have nothing in common and shouldn't be cast as each other. The reason TypeScript believes that is either because it's true or because you made some earlier mistake.
TL;DR: This article is about fixing your own mistyping and not about typing something that lacks any. It's not about the boundaries of your application but about the internal workings
Maybe my years of contributing to undisciplined codebases has made me bitter but TypeScript has ways to solve these problems and it hurts to see more attention brought to hacks than the interesting (and underdocumented) solutions TypeScript already has. Given that roughly half of the documentation is in the damn changelog, TypeScript is especially in need of independent content that highlight features (not hacks) of the language
I actually thought that calling out the `is` operator was useful, I have a generic utility method I call `filterFalsy` which takes `T | undefined | null` and returns `arg is T`, which seems decently safe, but it's interesting how it would fail when asserting between two types.
Unconvention 2 made me vomit, inlining a function like that would never pass code review, indeed if you remove the mutator from being declared inline it restores type soundness as you would expect
Unconvention 3 is important to learn, TS "duck typing" is not resilient to usage of spread/Object.(keys|values|entries)
Unconvention 4 is why I argue to use `callback: () => unknown` whenever you won't use the return type, it is the least restrictive and also implies to stay the hell away from that return value.
Fun article.