A little while ago, I was working on a change in a typescript-react app with a colleague. We ended up making a breaking change to a major feature, and — as a result — had to update all the usages as well. In one such update, we now had to use a numeric entity id that was present in the url. We used react-router with url params, and react hooks, and the code I wrote went something like this:
And as soon as I wrote this, they had a question for me:
Why not use Number?
Understanding the difference
Let’s dig into the details
Oftentimes in technology — as with a lot of other things in life — there’s no single
right
way to do things. There are several approaches, each of them with their own pros and cons. Over time, with experience, we form opinions — or our own “smart” defaults. In other words, there’s a first thing we try in every scenario. And the more times it works without problems, the firmer the conviction that it is our best option.
My smart default to get an integer from a string was to use
parseInt
. The two solutions in consideration by my colleague were:
Number(id)
parseInt(id, 10)
Yet, in this case, it’s more than personal preference. I have a couple of points on what we risk by using Number for the specified use case.
Conveying intent
A large part of coding is for the reader
I hate writing comments. Some people love me for it, and others hate me. I prefer to convey intent with code where possible. In that sense,
Number
and
parseInt
convey very different intents. They are
semantically different
.
Number
coerces (or converts) any value into a number.
parseInt
parses an integer value from a string. Given this,
parseInt
conveys intent better. In other words, it describes what I’m trying to do in a better way. By using
Number
instead, we risk conveying incorrect intent.
Functional correctness
Do they both actually do the same thing and meet our needs?
The two functions do different things.
Number
works with any input, but
parseInt
takes only string inputs. Technically, this is JavaScript with no types, and
parseInt
coerces any input into a string— but, that is an implementation detail. In non-technical terms, it
considers
any input you provide to be a string. If we provide a
null
or
undefined
input,
parseInt
will return an
NaN
(as expected, I hope).
I’ve listed some sample inputs and outputs of both the functions here. Some of the cases that might be baffling are actually reasonable:
Some important distinctions to note here are the different results for:
null,
boolean values,
empty strings,
strings which represent numbers in scientific notation,
strings which have explicit octal notation, and
strings which have non numeric characters.
In this specific instance, the differences in handling a null or empty string as an input matter. Resolving the absence of a parseable input to a valid integer value of 0 is not correct. By using
Number,
we risk being functionally incorrect.
Type safety
Can we catch issues that otherwise only fail at runtime?
This ties in to both the earlier points, but is unique to TypeScript. The previous two apply to JavaScript and all its variants in general.
Here’s the type definition of the two functions in TypeScript:
Because of this, TypeScript warns us if we call
parseInt
with an invalid input
.
If we use
Number
, we‘re left with no such type safety. This might seem insignificant, but I was able to show its value immediately.
In another usage that my colleague and I had to replace, we had this code:
This used to result in silent runtime errors. TypeScript could’ve caught them at compile time if we used
parseInt
instead:
If we wrote
Number(selected)
instead of
Number(selected.id)
, it would go undetected. If we used
parseInt
instead, we’d avoid this entire class of type-mismatch problems. By using
Number
, we risk type-safety.
Supplementary info
A few things I would like to clarify
We must treat the
NaN
s that both of these functions can return.
Wherever I show
parseInt
in code, it’s always with 2 inputs. The second argument
radix
is optional in the type definition above, and yet I do this. This is because
parseInt
has implicit octal and hex detection. The best practice is to always use an explicit radix
.
In our case, we handle this using a corresponding lint rule so we don’t miss it. It always helps to be explicit, so this is not too unusual.
We have only discussed a certain specific scenario — parsing an integer entity id from a url parameter. In certain other scenarios,
Number
may indeed be the best option — like dealing with scientific representations of numbers. I hope this article was helpful. The next time you encounter a scenario where you’ve to deal with numbers in strings, I hope you’re able to contemplate the case and make the “right” choice.
Let me know in the comments if you’d like me to address any other specific topics. If you know someone who’d enjoy reading this or find it helpful, please don’t forget to share this with them.