Discussion was locked because I moved away from DEV to blog on my personal website. If you want to chat about the content of any article, hit me up in Mastodon.
While a lot of programming languages that have a "nullish" type (null
, nil
, etc.) debate about avoiding it, JavaScript is the only popular one that has two, you read that right, two nullish types. One of the most common recommendations is to stick to using only one, and my recommendation is to only use undefined
and avoid null
. In this article, we will go over the reasons you might also want to avoid null
in JavaScript and TypeScript.
Why is so common to avoid nullish values?
The creator of null pointers (Tony Hoare) is known for calling his own creation a "billion-dollar mistake":
I call it my billion-dollar mistake (...) My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
When we use nullish values, what we want to express is that something is "not there", a "no-value". Generally, in typed languages, we express those as "optional values", because they can either be set or be nullish.
The direct implication of this is that we need to test every "optional value" for its type and also for the nullish value it can take.
Now, imagine how bad is it for a language to have two nullish values. We now need to test for not 2 different types, but 3. This affects negatively maintenance, readability, and overall code quality. Because of this is that the most common recommendation is to avoid nullish as much as possible, and in JavaScript, try to stick to using only one. In the following sections, we will go over some reasons why I (and many other developers) prefer undefined
over null
.
The nullish that the language uses
As Douglas Crockford (the father of JSON) put it in one of his talks, JavaScript itself uses undefined
all the time, so let's use the one the language uses:
let something; // This is undefined!
const otherThing = {
foo: "hi",
};
otherThing.bar; // This is also undefined
const aFunction = anArgument => {
// anArgument here is undefined if no value is passed
};
To use null
on all those scenarios, we need to explicitly set the values to null
, which will look like this:
let something = null;
const otherThing = {
foo: "hi",
bar: null,
};
const aFunction = (anArgument = null) => {};
I don't know about you, but for me...
What if I want to define a nullish value intentionally?
In that case, just assign undefined
to it:
const anObject = {
...otherObject,
propertyToNullify: undefined,
};
That nasty bug with the type of null
We all know at this point about the bug with typeof null
, that bug doesn't apply to undefined
which works as expected:
typeof null; // "object" π€·π»
typeof undefined; // "undefined" π
Why would we use a bugged value intentionally?
Smaller API responses
API response sizes are reduced drastically if we rely on undefined
instead of null
. Here's a response example using null
:
{
"foo": "foo",
"bar": null
}
Versus with undefined
:
{
"foo": "foo"
}
The case with Array
Array
is a special case, because when we create a new array of a given size, the items inside said array are actually empty
, not undefined
. This empty
means that if you check for their value, it will give you undefined
, but they aren't taking any space in memory (performance reasons), so if you try to loop over it, it will give you nothing:
const array = new Array(3); // [empty, empty, empty]
array[0] === undefined; // true
array.map(console.log); // nothing logs π€¦π»
The arguments in favor of null
When I say that you don't need null
, folks that use it a lot (generally coming from other languages that have null
as the only nullish value) get pretty mad about such claims. The most common response I get is:
null
is for intentional missing values, andundefined
should be used when the values were never set in the first place.
The first thing I think with responses like that is: Why would you ever need to make that distinction? Both are "nullish", and you don't need to differentiate between "intentionally missing" and "unintentionally missing". One common usage of null
is to do stuff like this:
const people = [
{
firstName: "Luke",
middleName: null,
lastName: "Shiru",
},
{
firstName: "Barack",
middleName: "Hussein",
lastName: "Obama",
},
];
But you can just omit middleName
when the user doesn't have one:
const people = [
{
firstName: "Luke",
lastName: "Shiru",
},
// ...
];
And you can set middleName
to an empty string if the user intentionally left that blank, if you really need to know that for some reason:
const people = [
{
firstName: "Luke",
middleName: "",
lastName: "Shiru",
},
// ...
];
And the TypeScript representation would be something like this:
type Person = {
firstName: string;
middleName?: string;
lastName: string;
};
Why would we spend memory with a null
value there, or bits with a JSON coming from the back-end, when we can just omit what is not there?
But the API is responding with
null
(maybe written in Java), so I have to usenull
all over my app as well.
My answer to that is: Use an API wrapper. Instead of "spreading" null
all over your codebase, update your surface of contact with the API so null
s are removed, and if you have any contact with the folks making the API, voice your concern of making API responses smaller by getting rid of null
values. You should try to avoid ending up over-engineering/over-complicating your app just to deal with null
when you can just avoid it altogether.
But in React I use
null
when I want a component to not render anything
You can use undefined
as well.
You have to type 5 more characters when you write
undefined
explicitly in your code.
Generally, you will rely on it implicitly (omitting the value), but even if we had to type it every time, is worth it compared to all the downsides of null
.
Languages without nullish
There are languages out there that don't have nullish values, and instead rely on Maybe
, which is a type that means "we might get a certain type or nothing". We can do a simple implementation of that in TypeScript like this:
type Maybe<Type> = Type | undefined;
So we might get whatever type we are expecting or undefined
. We can just use ?
as well when it's a property or argument:
const aFunction = (optionalArgument?: Type) => // ...
type AnObject = {
optionalProperty?: Type;
};
To deal with our "Maybes" we can use operators such as nullish coalescing (??
) and optional chaining (?.
), so...
// We don't need to do something nasty like this:
const greet = name => `Hello, ${name !== null ? name : "Guest"}`;
// We can do this:
const greet = name => `Hello, ${name ?? "Guest"}`;
// Or better yet, because we are using undefined, we can actually...
const greet = (name = "Guest") => `Hello, ${name}`;
Linting like a champ
If you're convinced that null
is not a good nullish value, you can avoid it from now on using this great ESLint plugin, and just add this to your linting rules:
{
"plugins": ["no-null"],
"rules": {
"no-null/no-null": "error"
}
}
Other sources
Here's a list of some sources by other devs that share my opinion about null
:
-
Abandon
null
in favor ofundefined
in Angular. - Null is bad by a lot of people.
- Why you should always use undefined, and never null by Fredrik SΓΆderstrΓΆm.
- TypeScript coding guidelines
- A StackOverflow answer.
- The Better Parts by Douglas Crockford.
Closing thoughts
My personal opinion about null
in JavaScript is "anything written with null
can be written with undefined
instead", but your mileage might vary, so as usual I close this article with a few open questions: Do you NEED to use null
? Don't you have a way of resolving that issue without it?
Thanks for reading this, and special thanks to the 3100+ followers that motivate me to keep doing this series! Remember that if you don't agree with something said here, you can just leave a comment and we can discuss it further.
See you in the next post of this series!
Disclaimer
This series is called "You don't need ...", emphasis on need, meaning that you would be fine without the thing that the post covers. This series explores alternatives, it doesn't impose them, so consider that before glancing over the post and ranting on the comment section. Keep it respectful.
Top comments (122)
1) Json doesnβt have undefined, also when you interact with other sources (db, cache,..)
2) null and undefined are totally different things, used in different cases.
When the value is null, you know it was defined
When the value is undefined, you know it wasnβt defined
Example, a validator should know if a variable is missing (undefined), or itβs presented without value (null)
Wondering how no one still mentioned 'in' operator:
Several reasons, I guess:
undefined
invalid, because within
you can figure out if it was intentionally set asundefined
, or is actually missing from the object.null
,in
will returntrue
as well, so that doesn't make it better thanundefined
, nor different. So another argument in favor of "they are the same".in
.TL;DR: Because is an argument in favor of the article, I guess I could even update it with that :D
You should explain that to me. I mean you're the ones saying "null means something is defined but intentionally empty", so you're the ones saying there's a need for that kind of thing ... I'm just saying that using
in
you can achieve the same thing withundefined
... in both cases we are saying something is "defined but not quite", just with different wording ... and I still don't get why you need that distinction π€£Well, I tried to find an example but cannot :)
And in this whole long holywar I cannot see a single example why null is better. JSON is one example, but let's imagine we delete all null keys from objects after getting data. Okay now we have libraries which are requiring null, and we have to convert undefined to null when using them. And that's all.
Maybe null make life easier when you are not using TypeScript, I don't know. With TypeScript you can see type in the code, and without it people are probably doing console.log(data) all the time to see data shape, and if values are omitted this is confusing.
Good article, thanks for writing, please keep doing it!
Well not only me, also other people here were trying to explain to you, but seems you just didnβt want to get it.
In my experience, consistency is one of the most important thing. Example: my service exchanges data with other services, they will have the same schema where they agree how an object looks like.
When other service sends me a kebab request with βketchupβ is null, I understand this kebab should be made without ketchup.
If the βketchupβ key is undefined, I understand that this kebab is on hold, because its definition is incomplete.
But if the βketchupβ key is missing? Wtf? Maybe the request was broken in the middle? Maybe the other service updated new schema and changed the key? Maybe the βketchupβ key is deprecated?
And it turned out it was omitted because some guy thought itβs cool to save some charactersβ¦
I understand thatβs there is always a way to handle it. But ask yourself: Would you like to write inconsistent code with confidence, or youβd prefer monkey patches here and there with thousands of conditions?
Happy Befana btw
You can use TS without using TS, by typing with JSDocs. Try this out in VSCode:
Thanks a lot for that last line, Roman. I greatly appreciate some good vibes in this comment section β€οΈ
If you really think about it, we can have that same consistency with
undefined
instead ofnull
. At work we have contracts between the API and the client. We translate the type definitions from the back-end in.d.ts
files we can use from TypeScript and JavaScript. When something is optional in the back-end, the type isType?
, notType | null
orType | null | undefined
, so we know with confidence that we can send a value orundefined
(namely, not send it). Same applies for the things we get of the back-end, if something we fetch could be missing, then the type isType?
again, which means it can be undefined, and we deal with that either with default values, optional chaining and all that sweet tech we have to deal with nullish values.Long story short, you have a convention to use
null
andundefined
, that convention could use onlyundefined
as well and work ... and this still doesn't negate the fact that you can useundefined
everywhere, except when you talk with the back-end.I updated the article adding some other folks that have a similar opinion to mine (even a feature request in consideration to update Angular so it only uses
undefined
).Offtopic, I was reading your previous article and wondered why to compose functions in this way
update('user')({ ...data })(user)
, looks like you are intentionally keeping all functions with one argument, and I don't understand why not to have a function with 3 arguments instead. I'd love to read a new article from you "You don't need second argument" explaining this pattern. Looks crazy tbh.Also "You might not need" is a way better naming, because people need classes in Angular, indeed they need mutation in Vue, null in response from database.
Is a pattern called currying, it allows you to create functions from functions and make everything more reusable:
Maybe is not the best example, but basically you can create
updateX
and reuse it all you want, with different values for different objects ... compared to doing the same with 3 arguments:We need to write update and pass every argument with every use. Currying is one of those things that when it "clicks", you never go back. A popular library that uses this a lot is Rambda.
I considered that rename a few times, but my point is that you don't need it in your own creations, that's the main reason I have my articles set so that is more for "almost experts" than "beginner". Beginners using other folks tools need to learn all this stuff, way later we learn about the things we don't need to code, but might need to interact with somebody else's code.
Thanks, but still more in depth article would be awesome. By your example I can see that in plain JS carrying is worst thing to happen: if you see somewhere in the code nameFoo, you'll probably assume it's a simple string, while actually it's a function which takes object and returns new object with updated name property. With TypeScript it's still very confusing, okay we can hover and see the type, but I assume it will show a complex barely readable type. And I hardly imagining how it is even possible to write all this types, how much complex generics will be needed for this, how much unions.
In TS function "update" has to take a union of all table names. And what if for one table update should have different implementation?
While in true FP languages there is a function overloading and you can define them separately for different tables, which is removing all downsides and sounds awesome. But JS/TS is not such language so it's a good question if the same approaches really worth to be used here? (I didn't ever worked with FP language)
I assume you are following this carrying approach on real projects and you know how to deal with difficulties on this way, so let me repeat myself, isn't it bright idea for article? There are a lot of articles on the topic ofc, but they only sharing basics and explaining nothing useful about how to deal with it in real world, when to use and when to avoid. Not insisting of course, maybe there are such articles and need to search harder and play with it myself
Sure thing! I'll create an article about currying and mention you on it for pushing the idea if you don't mind ^_^ ... about the TS implementation, that
update
function could look something like this:I do love to do TS + FP, so creating an article about this might be interesting for other folks as well π
Awesome! No one yet wrote article in my honor, I'm flattered :)
I wish it could explain "Why" and "When to use" no less than "How to use", so pros and cons.
So far carrying looks like a lego and there is something in it, definitely there is something about it.
You donβt need βinβ /s
Btw itβs fun when
Yup, it kinda sucks, but is preferable to return
undefined
for something that isn't defined ... you can still check if a variable exists before trying to access it, by usingtypeof
:Accessing properties is a better DX, and with
?.
and??
is even better:Iβm not talking about the check. Iβm just trying to point the inconsistency between two cases about the same thing.
But that's the thing, they aren't the same thing. One is trying to access something not declared in the scope, while the other is trying to access a property of something that is declared in the scope. You can test both with
typeof === "undefined"
, but if you try to access something undeclared you get an error, which sucks, but that's how it works. Nowadays is very difficult to end up in this scenario tho, because editors will let you know if you're trying to access an undeclared value.Json can model undefined through absence of a key. Otherwise fully agree.
When reading a model, I would generally consider absent or undefined values an error, or eg a just in time migration that needs to still happen :)
Otherwise the contract is too fuzzy for my taste
Thatβs the thing.
Eg from a json response, a βpostβ which has βdeleted_atβ is null means itβs not deleted. But if the βdeleted_atβ is missing, it should be considered as invalid api response
That depends on the API implementation. This is like saying:
Which is kinda like a Straw Man. If the API has a poor implementation that requires
null
, that doesn't mean you need to usenull
in your entire codebase, that just means you need to use it in your surface of contact with said API.Sorry but I wouldnβt consider an api which has consistent response for nullable object, as a poor implemented api..
Me neither, that doesn't have anything to do with
null
. You can just omit something when is "nullish" which translates to .... you guest it,undefined
, and it has the added value that payloads are smaller πAbsence and null are different things, as said.
What constraints are you under that these minor payload sizes matter? youβll probably get more out of gzip than omitting nullish.
Payloads are smaller by omitting some characters, good.
But if it causes confusion between null (value is set as no value) and undefined (value is not set), I wouldnβt take it as a good thing.
"Absence and null are different things" ... how? The size of the payload doesn't only affect the request, also the parsing. You're literally loading something trough the wire and in memory that you could omit, just to use
null
X_XWith tech like TS, contracts are actually pretty easy to define. There are tools out there that turn all kind of type definitions in
.d.ts
files, that you can use from TS or JS with editors such as VSCode to get the type checking. If an API defines something as optional, in TS that'sType | undefined
, so you can omit that key or send thatType
and it will work :D ... if the types change in any way, you should update your type definitions accordingly (using the same tools), and you'll get errors directly in your editor πundefined
πundefined
.null
orundefined
, so I still don't see much value fornull
in this scenario. If a value can either come or not, then you can define it as optional (meaningType | undefined
), and in the front end you can just dovalue ?? "default value"
orif (value === undefined) { throw // ... }
or whatever πundefined
πβWhy would you ever need to make that distinction? Both are "nullish", and you don't need to differentiate between "intentionally missing" and "unintentionally missing".β
This is quite often needed? How do I know if a user set this as βundefinedβ or if it was not set by the user. Hence the need for null.
When a value is "optional", that only means it can be defined or not, it can be a type or be nullish (the
Maybe
type I mentioned). So something that's optional has the shapeType | undefined
, orType?
So if a value for a user is let's say a string name that might be intentionally left blank or not, then the type for it is
string | undefined
orstring?
. So if the user set it to be empty intentionally, then you can set the value to""
, and if not then set it toundefined
.You can then send it like this if the user left it blank intentionally to the back-end:
And if the user didn't set it at all, then:
Trust me, every piece of code written using
null
in JavaScript/TypeScript, can be written withoutnull
, and it generally ends up being cleaner.Sorry but when a value is optional, it doesnβt mean it can be null.
Eg a βpizzaβ with optional βextra_toppingβ. If you donβt want to have extra topping, it should be null.
But if it doesnβt have βextra_toppingβ (value is undefined or absented), the object should be considered invalid.
Why?! If it doesn't have
extra_topping
then it doesn't have any, and if it has that property then it has extra toppings ... the one defining that "rule" that if something is missing then is invalid is you.If you call a pizzeria to order a pizza, you donβt mention extra toppings. What should they think?
1) You donβt want
2) You forgot to mention
So much mentions of pizza are making me hungry π€£ ... Answering:
Do you say "keep the default toppings" every time you order?
You didn't answer the question. The question is how would the pizzeria know if:
1) You don't want extra toppings
2) You want but you forgot to ask, or you didn't know that they offer extra toppings
1 should be null, because you stated it explicitly
2 should be undefined, because you haven't decided yet (yes, AKA you haven't defined it yet, that's why it's called undefined)
Did you ever actually bough a pizza? π€£
Maybe this is not the best analogy for your point about needing 2 different nullish values?
Alright, if you consider it's problem as a client, I have nothing else to say :)
// And you're right, I've never bough a pizza actually..
Gotta love how you completely skipped over the issue.
So how in the above do you suppose isSet would work since all youβre talking about is typescript types which donβt actually exist at runtime.
const settings = something.
if(isSet(something.option)){}
Using falsy values is like pointing a gun directly to your foot ...
And you'll get:
Option: Intentionally unset
if the value is""
.Option: foo
if the value is"foo"
.Option: Not set yet
if the value isundefined
.Really, you don't need
null
, or at least not for this.Yes... because everything is a string and everything is a single type. π
All this post comes down to is βI donβt like nullβ. You donβt seem to have an understanding of the use so you make up reasons for it not needing to exist.
Donβt like it then donβt use it but it exists for a reason and the fact you canβt accept that shows what kind of developer you are.
It seems somebody skipped over the disclaimer at the bottom of the post! π€£ ... should I assume that you can't live without two different nullish values, then? How do you think languages that only have one of them work? How do you think languages that don't have nullish at all work? If other languages can work without two different type of nullish values, why JavaScript can't?
Obviously there are different types, and different possibilities for optional values, that doesn't mean we then need to do all types
Type | null
and rely on falsy, that only means that if you have different types, you need to deal with them in different ways. Usingnull
always is a really poor solution.And if the number of downloads of the ESLint rule to avoid null tells us something, is that is not "only me".
I've been using Lua for years and never felt the need to have a second non-value. You either have
nil
, or you have a value.If you want a special value that's distinct from everything else, you just use an empty object and compare to it explicitly.
That's an argument that doesn't hold up under further scrutiny, though. The "single nullish type" causes its own problems that JavaScript actually resolves.
Let's take Java as an example. It only has the single type
null
which is the default variable assignment for any object type. The "Map" interface in the Java standard library (Java's dictionary data structure) specifies that it returnsnull
when client code gets a value from the map using a key that the map doesn't know about. Unfortunately, it's well-known thatnull
is often also an actual value associated to keys in the map. This single flaw is one of the leading reasons why NullPointerException is the number one exception observed in the logs of production Java applications.That situation is resolved in JavaScript, because the absence of the key is represented as
undefined
rather thannull
which disambiguates the cases.You mean to say that all languages with only one nullish value, or no nullish value at all didn't figured out something that JavaScript did? In any other languages, if something is "optional" (meaning it can be a certain type or nullish), you just have to check before using it. If you don't, you run into issues (one of the main reasons some languages have the concept of
Maybe
instead of having nullish). In JavaScript you can still run into exceptions (TypeError
) if you try to run something that's nullish or try to access a property inside a nullish. Both are solved using nullish coalescing and optional chaining, so you can still use either one of them. The solution to the problem isn't having two different nullish X_XWhat I'm pointing out is that it's not a very good argument to say, "if other languages can work with a single null type, why can't JavaScript?" Just because other languages figure out ways to exist with only a single nullish type, doesn't mean anything in terms of the advantages of having more than one nullish type.
As a complete aside: you're actually making the argument for more than one nullish type yourself when you deflect to creating an optional or maybe type. There's also the "Null Object Pattern." These are all attempts to grapple with legitimate issues in programming. Using "null" and "undefined" together is just a different attempt.
At the end of the day, I could avoid the use of both null and undefined in my programs. That doesn't make it a good idea. I saw that someone made an HTTP server in pure BASH. Interesting? Sure. Does that mean I don't "need" HTTP servers? No.
But the argument about languages having only one nullish, or not having any, is to point out that generally "nullish" should be avoided, and with dealing with it we should be as straightforward as possible. Having 2 nullish is the contrary to avoiding nullish π
The article shows several scenarios in which you might need nullish, and how you can deal with it only using a single nullish value (
undefined
). Those same problems could be solved only usingnull
and avoidingundefined
, but you end up doing extra work when you could just use the nullish that the language uses.Why not? Is commonly known that nullish values are a bad idea (the creator of the null pointer itself regrets that decision). My point in the article is that if you have to use nullish, at least you can use the one that JS uses.
Is there an example in this article that's using
undefined
and makes you think: You neednull
for that?The whole "other languages do well without two non-values" argument doesn't mean that JavaScript should do the same; it just means that there's no need for JavaScript to specifically choose this solution to the problem.
If you want an actual reason not to have two non-values, it's that this just seems very arbitrary. Why two, why not three? And if you're ascribing more and more semantics to the different kinds of value-absence, shouldn't you also expose this possibility to the user? At this point, just let users define their own values that are treated as falsey by the language and add a
FalseySymbol
counterpart toSymbol
.I don't think it seems arbitrary at all. The language specification is not arbitrary when it describes
null
as meaning something explicitly set andundefined
meaning something, well, undefined. Just because they both evaluate to "falsey" doesn't mean there's not a clear difference to their actual meaning. The reason those two exist (and not three or four or five) is because there's not another clear cut solidly-defined use case for it. But for bothnull
andundefined
, they both have a clear unambiguous meaning and plenty of use cases.You misunderstood my point; both
null
andundefined
are not at all arbitrarily defined; what is arbitrary is exactly having two of them.If you're going to distinguish non-values of different kinds, why only two? Why not add a third one for "no selection", or a NaN-like value for "unknown" values? And I'm sure many problem domains would come up with their own non-values that make absolute sense to distinguish from
null
andundefined
.As I said, at that point a more elegant solution would be adding
FalseySymbol
orNullishSymbol
or both and leave it up for the user to define specific non-values that hold whatever semantic meaning they want.This is a neat case where there's exactly two reasons why a value could be empty. But in other cases there could be many different scenarios.
If you really want to use your own "empty" value, you can simply construct a symbol to represent that specific state
Hi, I would strongly disagree with this statement. In my opinion you definitely want to use explicit null in many cases. The thing is, null is a quite handy tool if you use it properly (eg. "Hey, I want this value to be unset but defined!"). Also, I noticed you are considering
typeof null === "object"
as a bug, which really annoys me. It is not a bug and the reason is lying in the fundamentals of the language (prototypal inheritance). Please be careful with such a statement, since it is misleading and generally wrong! My apologies being short and not going into the details though. Happy new year!While this is by design and therefore not a bug, I think this combination of behaviours is what makes for a very bad design. If
null
is an object, then it should behave like one.But .... it is a bug, it was never patched because ... welp, that would break the web (like many other things).
My point is mainly: Why do you need to do that distinction, when they are both "nullish" values? How do you think languages that only have one or have no nullish values deal with this?
For me at least, is kinda like the chicken and egg, folks don't actually "need" null, they generally just find ways of using it. Just for your consideration, there is a disclaimer at the bottom of the article in which I clarify that "you don't need" doesn't mean "you should never use", is just like saying "you don't need fast food", meaning you can survive without using it and you should avoid defaulting to it to solve issues that you can solve with better tools.
Sure, I'm considering it not-a-bug for the same reason, it is just the part of the language and you have to deal with it :) However, you can exploit its potential. I personally prefer using
null
overundefined
when I want to consider an asset to be explicitly empty (but still defined in the scope of the current code).undefined
is the default value for a non-existent value and I think it is unsafe to rely on this, because it can cause runtime errors more often (think of globalwindow
in Node environment). It's all about practices and personal preference. In my opinion,null
is a handy stuff and I'm happy to use it.100% agree with this! My series of "you don't need" is not about imposing my style, is more about making the readers wonder: Do I really need that? Or I'm just used to it? I used to be a C++ hardcore fan, class all the way, and my first entry in this series is saying that we don't need them because I feel happier not using them in JS/TS.
About
undefined
introducing bugs, the vast majority of them are solved with the good??
and?.
, so you can actually do stuff like:Yup, exactly. Hail to nullish coalescing and chaining! I love how people eager to build the language forward and constantly improving it in every year. Classes are not quite making sense in JS to me too. Although, it is important to be able to adjust your personal preference to the environment your working in. Everyone is doing stuff differently, but we usually work in teams, so... you know the drill.
Indeed! My No.1 priority at work daily is making my teammates lives easier, building components, utils and libraries for them. More than once I had to change something I personally like to something that adjust to them better, and that's ok. They are all happy without having to deal with classes, but if they weren't, I would be doing classes even if I don't use them in my personal projects, just to adjust to them β€οΈ
No ... you're saying "folks that use it a lot ... get pretty mad" - I'm not mad at you lol ... but I still see value in explicitly assigning null, and undefined is a distinct case.
The point is that "undefined" is automatically there, while what I want is for programmers to explicitly and deliberately initialize their variables - be it to null, or zero, or an empty string, or whatever makes sense. But explicitly initializing a variable to "undefined" makes no sense (because by definition it already has that value).
If you really want to get rid of null pointers and of "having to check for null everywhere" then you should introduce an "optional" concept (like Rust has, or even Typescript).
We have "optional" with JS, you just use default values (only works if the passed value is
undefined
), nullish coalescing and optional chaining (this last two work withnull
as well). TS after all is just a superset of JS, the types it uses are just the explicit version of JS types (also seen in JSDocs).Thanks for not getting mad. Trust me, there are some folks here pretty vocal about their opinions not matching with mine π
People should remain rational, getting all emotional over tech debates is a bit stupid lol
No matter how much you donβt want to use null youβre going to end up using it anyway. Itβs going to come in from libraries, input, and data sources.
You still don't "need" it, you'll just have to use it when you use those libraries. You can also use validation libs to turn nulls into undefineds. The important thing is avoid using it yourself in your code, not su much the contact surface with code outside your control.
You don't "need" stoplights but not knowing about and incorporating them into your driving will have disastrous results.
That's not a good analogy -_- ... I never said you don't have to know about nulls, how they work and how to deal with them if you find them, I said you don't need them in your code, and you are generally fine with
undefined
.I mean, you also haven't said to "deal with them if you find them" (which by the way means having null in your code).
Might be a language barrier that's complicating this conversation, but I said this already several times in the comments: You should keep
null
in the surface of contact with things that "need" null, like APIs. That means that you'll have to writenull
to interact with those pieces of code that didn't figured out how to work withundefined
, yes ... but still doesn't mean you need to writenull
in the code that doesn't interact with those APIs.You can keep
null
in the block in the middle there, only. And in your code just useundefined
. Think it this way, if you invite a celiac friend to eat outside, you need to buy something gluten free for them, but do you eat gluten free as well when you don't need to?It sounds nice, but null is part of the JavaScript language itself. It has an explicit meaning in the specification:
So that "surface area" you're talking about isn't going to be limited to APIs. And when you share a JS codebase with anyone else, the approach of not using
null
is going to be untenable, and, even if you don't share, your JS is not going to be idiomatic.I mean, I worked for years now with different teams without using
null
, and nobody misses it or says "we could solve this withnull
". I said it on the article already, but making that distinction between "intentionally absent" and "unintentionally absent" doesn't make sense, because is just "absent" and you need to deal with it the exact same way.That doesn't mean it's idiomatic.
I can see that it doesn't make sense for you, but look at all the commenters here with plenty of examples where it makes sense. And I even gave you one in another thread.
If we have to use this article as a metric, we have 10+ negative comments and 10+ folks liking those comments, so let's say 20+ total. And then we have 55 likes to the article itself, 17 unicorns, and 6 positive comments, so let's say total 70+ ... I knew I would have some folks that wouldn't agree (that happens with every article in this series). You keep saying is not idiomatic, and my point is that you're just saying "the same thing with two different words". The distinction is not needed, you might use
null
to make that distinction between two "optional" values, but you don't neednull
for that.The point of the article is to actually make you think if you need it, I know some of you are used to using
null
or you do that unnecessary distinction betweennull
andundefined
, but the idea is to make you actually think if you need that distinction, or not. Have you actually tried to code without usingnull
yourself? Maybe you already did, and you missednull
every single day, but my experience (and the experience of many other folks) was the exact opposite.The point about idiomatic JavaScript has to do with working with JavaScript developers. If you write in a non-standard (not idiomatic) way, you make it harder for others to use your code or integrate with it, and that can lead to bugs.
These reasons to use null seem compelling to me:
How can use less ""features"" makes something lead to bugs? The only way of leading to bugs is if a dev assumes that something will have a
null
value, for example, and my function returnsundefined
instead ... but the same could happen the other way around :/ (not to mention both are solved the exact same way)Being in the language is not a "compelling" reason, because we avoid things in the language all the time that we know are bad.
eval
andwith
are still part of core JavaScript, and you wouldn't use those because you know that they lead to issues of all kinds.I already said that you can then deal with
null
only when working with those. Not to mention that data sources can also returnundefined
, it depends on how are they made, and the actual user input is nevernull
, you can get "null" of user input only if the handler for said input returnsnull
.So for you idiomatic is: It's in the language + folks use it. From that definition, being "idiomatic" is not a good reason to use something from my point of view.
I keep asking why is that distinction even useful? They are both missing values, both needs to be handled the same way ... so why is useful to differentiate one from the other?
Exactly. Consider the matches() method on string. It returns
null
to indicate that there are no matches found. Let's say we take your approach and wrap this in our own method where we returnundefined
for this case instead. Now you have a client of your method who is a JavaScript developer. They check fornull
to see if no matches were returned, but you have overwritten this withundefined
. And that's a fairly benign case.This is often known as the Principle of Least Astonishment or Surprise. Users of your code should have an expected experience.
Do you actually do
if (matches === null)
nowadays? You don't need a wrapper. You can use nullish coalescing and avoid a lot of pain with bothnull
andundefined
(again, you handle both cases the same way). So you can do something like this:Which works with both
null
andundefined
. Instead of having to write:And I still don't see where the distinction between
null
andundefined
is valuable here. With the same example of the util, if someone does a wrapper of something that generally returnsundefined
, and they make it returnnull
, it could cause confusion as well. You should't code assuming the nullish output of a function, and you still can be resolved similarly to the above issue.BTW, if I had to write a wrapper for
match
, I would do it something like this:This way you can not only curry it, but always be sure you'll receive an
ArrayLike
from it instead ofRegExpMatchArray | null
which is far from ideal.It seems that you asking this question proves the point. You have no idea what I (or other clients of your code) are going to do. I may do that or use the fact of
matches
returningnull
(and notundefined
) in probably a few dozen different ways. And when/if you change that, you introduce the potential for bugs, as previously pointed out.The idea of a "wrapper" came from your comments, not mine, though.
I am not sure but I'll give the benefit of the doubt here and assume that you are not willfully missing the point. So to reiterate again: yes that could cause confusion as well, and the very fact of this confusion is why JavaScript developers should not ask "Do I really need to use
null
?" and should, instead, use bothnull
andundefined
consistent with general practice. It is the standard practice (the idiom) in JavaScript fornull
to represent a purposefully-set value andundefined
to represent a value that was never set, and this is in the language specification as well as the canonical books and documentation.My point was that you shouldn't assume the output of a function. In your previous comment you basically said that you "expect" something to return
null
. Based on your same logic in this new comment: "You have no idea what I (or other clients of your code) are going to do", so why is bad for me to assume you'll know that you shouldn't domatches === null
, but is good for you to assume that the function will returnnull
instead of actually checking the return type of the thing you're using?But that's the thing, if you use a function assuming it will return either
null
orundefined
, why is it on me as the dev and not on you as the consumer of that function? I already said this, but the other way around your same argument applies, if you create a function that I expect will returnundefined
, and instead you decide that it will returnnull
, then it's my problem that I was expecting your function to return something I wanted it to return, instead of checking the typing or using something like??
.And I still don't see where the distinction between null and undefined is valuable here. With the same example of the util, if someone does a wrapper of something that generally returns undefined, and they make it return null, it could cause confusion as well.
Trust me, I'm giving you the same benefit. I added sources to the article for y'all to explore, because my personal experience of not using
null
in personal projects and at work, and not missing it at all, and explaining that the "distinction" between intentionally missing and unintentionally missing value is pointless because they are both missing and need to be dealt with the same way. Maybe checking some other sources besides my article adds some light to the subject, but ifnull
is so necessary, how is it that Angular is considering droppingnull
, TypeScript doesn't usenull
in its source, and folks like Douglas Crockford, with years of experience in JS, don't usenull
at all and just useundefined
instead?As @darkwiiplayer pointed out several times already, that "need" for multiple nullish values is trivial, having 1, 2 or 10 different nullish values with differences you define with conventions, doesn't take back the fact that they are all still just nullish.
Every time I ask: Why do you need to do that distinction?
The answer is pretty much "because
null
exist in JavaScript for that", but that doesn't actually answer the question. There are several things that exist in JS to be used for something, and we realized that there are better ways to do that same thing and not use that feature (eval
andwith
are the two that I mention constantly).We might never agree on this, but the questions I leave to you are:
Do you actually tried to code without
null
at any point? Do you ever worked with a library/framework or codebase withoutnull
and missed having it? Because I sure did tried doing it "your way" in the past, and once I did the "transition" I never went back.A valid use case for null is when you want to iterate over object properties and do some logic if corresponding values are empty. You usually don't want to write things like typeof string && "". What about numbers? What about booleans? Angular for instance uses it in its Forms Module. If a non-string field is empty, its value is null. If you reset any field value its default is null. So to me null is absolutely legit and not synonymous to undefined.
That doesn't make much sense for me. If something can be either
string | undefined
, then adding| null
to the mix doesn't help much here. You end up having to test for yet another type, when you could just keep it simple.This two behave exactly the same:
So
validate
there could be implemented like this:Which would work exactly the same for both
null
andundefined
. What's the advantage ofnull
overundefined
here?And about the argument of "Angular uses this in its Forms Module", just because a module/library/package uses
null
, that doesn't mean that you need to usenull
all over your app as well. Not to mention that if a non-string field is empty, it could beundefined
as well, same if you reset it, is just an implementation detail of that forms module, which doesn't mean you need to usenull
in places where you're not interacting with that particular module.Good points! My examples were not that good. Still, in some other place of my program I might want to directly access the object property like foo.bar and if it is undefined how can I be sure that the property even exists? I mean they are semantically different and have slightly different behaivior and I embrace them both as features of the language. You are correct in your wordplay, you don't need null, but I want to extend this and say that you may want it. For consistency, readabitity, because of business requirement or for whatever other reason. This is not a "goto"-situation. If you choose wisely your code will only get better. Like in Typescript there are types any and unknown which are in many cases interchangeable without any side effects, but... unknown is safer and sometimes you really need any.
You might be one of the first devs to comment here with the "opposite view" but you actually understood the post π€£ ... Indeed, the idea is that you don't need
null
and you can useundefined
to express the same things better, but if there's a library that only works withnull
, then you have to use it there. The main idea is that we shouldn't default tonull
and just use it when is actually necessary. Some folks think is necessary to make a distinction betweennull
andundefined
and I (and many other devs) disagree with this because they both represent a "no-value" or "nullish", but that doesn't mean "you should never usenull
", that only means "you should use it when is actually necessary", like APIs or libs that only work withnull
.About
any
vsunknown
, I might write a "you don't need any" showing how you can useunknown
everywhere, safely, even if you want to be lazy as you'll be using justany
βΊοΈI might be wrong, of course, but I am still not as convinced as you are. They are simply different and we would probably have quite heated debates regarding some special cases if we were working in same team. And I would also install a swear jar for such words like "nullish" or "falsy". My code is strict and explicit af π
Although, to be completely honest, I also have to admit that we really did forbid null in our previous project and mapped all null values as undefined in all our API responses. Didn't regret this decision, in our case (with good models) undefined worked as "empty value" really good, felt natural and the only places where null was really inavoidable were API and Angular forms.
Btw, I have a good example where you really need any. It's JSON:API standard which defines one of the fields as object, so the best you can do on the most abstract level (before any extensions with type narrowing) is something like Record<string, any>.
But you could also do
Record<string, unknown>
, which is like saying: I know this is an object, I just don't know the type of the properties inside of it ... way better than havingany
and accidentally calling a method or passing it to a function without first checking what that is. I seeunknown
as strictany
, you can't use it unless you first figure out what it is. And I love it.I also code pretty strictly and I still see not benefit using
null
at all (unless, as we said already, I'm interacting with something that needsnull
). And I share that experience you had, of migrating codebases to only usingundefined
, or having thatno-null
ESLint rule, and the best thing about that experience is that not only me, but nobody in those teams missednull
(that's also why I shared that talk given by Douglas Crockford, because he had the same experience, years ago).You're right. And I am stupid. Of course, unknown can be narrowed same as any. Simply forgot that.
You're not stupid! I used to use
any
quite a lot beforeunknown
was introduced, so it's ok if you're still checking before using it. The main difference is thatunknown
forces you to do that check, but if you still do it is ok!The thing is that today I really never use any (except for Angular and such). Absolutely forbidden! But I also wrote the implementation for jsonapi before I learnd unknown. So... π€·π
That's what happen to folks like us that were in this TypeScript game for a long time x'D
Definitely food for thought!
I personally over rely on null as it works so nicely with databases, but that is just laziness on my part and you are right that stripping null values instead of validating them (which does require some thought so you donβt open a massive security hole) does seem cleaner!
Next side project going to give this a go!
Now can you please stop this series as I am running out of things I can use! ππ€£
Let me know if you hit any wall while trying to get rid of those nulls, and I'll do my best to help out. In my personal experience I even noticed some speed bumps in requests/responses because payloads were always smaller π
Thanks Luke, great to know as I am sure there will be something and I imagine there wonβt be much out there in terms of advice on something like this! π
I've seen people give entire lectures about when you should use undefined and when you should use null. I always found that a bit silly. We have better things to waste our attention on. I just always use undefined unless forced to do otherwise (by some library that does
=== null
or such).Patch api; omit (which is usually synonymous to undefined) is βnot changedβ, null is empty value.
You took string as an example, but think also about numbers (-1?) or objects etc
We could change our API handler functions/methods in the front-end so when they do a patch operation we turn the intentional unset values into
null
s that the back-end will understand. Still, an API that usesnull
from the front-end to clear values in the back-end doesn't look like a great idea in general. Ideally you should have some kind of option in the endpoints to unset values, if we have updates and unsets in the same patch endpoints it feels like an accident waiting to happen.