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:
- 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.
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.
Do they both actually do the same thing and meet our needs?
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:
- 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.
Can we catch issues that otherwise only fail at runtime?
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.
A few things I would like to clarify
We must treat the NaNs 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.