Invalid values and the nullish coalescing operator in JavaScript

5 min read Original article ↗
← Back to all blog posts

Published 1 month agoUpdated 3 weeks ago


Abstract

After working as a JS/TS developer for quite some years I've grown quite used to seeing || "" or ?? "" strewn around codebases as a quick fix to "remove" nullability, usually due to some value which you assume is set, but can't "prove" type-wise. I think this is a code smell, and I generally think it's not the right solution. In this post I try to explain why I don't think so, and provide some patterns that could help combat the downsides introduced by this pattern.

If you've ever touched JavaScript or TypeScript, I'm sure you've seen something akin to user?.name ?? "" in your or someone else's codebase. It's a quick and dirty fix to combat the fact that the underlying type is string | undefined even though you might be convinced that it won't ever be undefined.

But if that's the case, why do you treat "" as a valid option? By which I mean, if you truly believe that undefined isn't a valid state, why are you implementing a "solution" to that case by using the nullish coalescing operator ??? By doing this, you're opening up for the possibility of showing a UI where the name is "". Is that really a valid state for the UI?

My philosophy here is that we should attempt to avoid implementing code which could lead to invalid UI states. That means that if the value of the right side of the nullish coalescing operator (what a mouthful) doesn't make sense as a value for the variable, then it shouldn't be there. It should rather be checked if the value is undefined, and throw an error, return an error modal/page/message, or by some other method, indicate that an error has occurred.

Same goes for backend code — this isn't a problem limited to the realm of frontend. process.env.MY_ENV_VAR ?? "" is something I've seen time and again. If "" isn't a valid (in the sense of business logic) value for MY_ENV_VAR, i.e. a sane default, which is what I think it should be, then this should error. I'd much prefer to see

if (process.env.MY_ENV_VAR === undefined) {
	throw new Error("MY_ENV_VAR is not set")
}

Rather than seeing a runtime error since MY_ENV_VAR turned out to be an API key and "" wasn't good enough to call the API.

Personally, I've come to see this ubiquitous string of symbols, ?? "", as the JS equivalent to .unwrap() in Rust, which for the uninitiated forcefully converts Option<T> to Tpanic-ing if the Option was None. Translated to TS this roughly translates to making T | undefined into T and throwing an error that quits the program.

In Rust, however, you're forced to reason about the "seriousness" of calling .unwrap() as it could terminate your program. In TS you're not faced with the same consequences.


Update 29th of November 2025: A bit of clarification regarding the comparison of .unwrap() in Rust to ?? "". First of all, comparing the two, functionality-wise, is wrong — and I should have made that clearer. The Rust equivalent would be .unwrap_or_default() or .unwrap_or. The reason I wrote .unwrap() was because I've seen the two used to achieve the same result, being reducing the type from Option<T> to T, or the equivalent in TypeScript without properly handling the None case. This was a mental leap that I made while writing, but didn't put into words well enough. Forgive the vague language, but I compared them based on feeling. I think this comment on HackerNews summarises my intentions quite well.

As someone pointed out on Hacker News, .unwrap does actually terminate execution as opposed to allowing the program to continue running with invalid values, but I would still advise against using it. Since it panics, and usually terminates the program, I think it's better to handle it another way. If possible, perhaps converting to a Result<T, E> when there's no reasonable fallback value using .ok_or.


The goal, in my opinion, should be to avoid writing code which could lead to a bad state of your program. If it doesn't make sense that user?.email is "" then that shouldn't be implemented. I'd rather have it fail early and clearly than trigger some obscure failed state during runtime.

In the real world, however, I understand why people do this. I've done it myself too — even though I'm not proud of it. It's quick, you often know that the "error" state won't ever be triggered, and not throwing an error can allow the rest of the website to continue functioning. Even though I believe this is a short-term solution that might come back to bite you in the future.

So to provide an actual "solution"; where possible, consider what the actual valid values are, and if you can't fall back to one of them as a default, throw an error. If you still want to return "" and risk breaking your UI, log an error, but don't throw. That way you will at least know when it happens.

As I haven't implemented a comment section yet, if you want to leave a comment, you can use the guestbook on the front page.