JavaScript: Why Named Arguments are Better than Positional Arguments

Aditya Agarwal
Bits and Pieces
Published in
5 min readAug 6, 2020

--

By passing arguments as objects, we can decrease bugs in our app. That’s always an important thing but even more so when sharing reusable functions/components between apps (for example, using tools like Bit).

This approach is called named arguments. Read the full article to understand why its better.

Example: A React component shared on Bit (Github)

Before talking about named arguments let’s first clearly understand positional arguments and the problems they can cause.

What are positional arguments?

You must be pretty familiar with positional arguments even if you heard the name for the first time.

Normal functions using positional arguments

The greet function takes two arguments- firstName & lastName. The caller has to make sure that the firstName is the first argument and the lastName is the second argument. The important takeaway here is that the name of the argument doesn’t have any significance. The only thing that matters is the order in which the arguments are passed.

This familiar approach is called positional arguments. It is usually fine for cases where you pass one or two arguments since its hard to mess up the order of arguments. But if you have to call a util which takes 6 arguments it would be hard to remember the order of arguments to pass. You don’t want to pass the password in place of the username argument.

Problems with Positional Arguments

Positional Arguments are pretty straight-forward to do but you will face some challenges with them.

1. Can’t skip middle arguments

Say you have changed the greet function from earlier such that it takes 3 arguments now — firstName, middleName, lastName. Since many people don’t have a middle name you want to make middleName an optional argument. The only way to call greet function with only firstName and lastName is this.

greet('Aditya', null, 'Agarwal');
// Correct ✅
greet('Aditya', 'Agarwal');
// Incorrect ❌

You can’t just provide firstName and lastName. This problem becomes more pronounced when the number of optional arguments increases to let’s say 5. Now you have to provide 5 nulls just to be able to provide arguments after those.

2. Adding types to positional arguments is less cleaner

Nowadays adding types to your utilities is becoming very common. With positional arguments you have no choice but to inline the types along with the function definition. This can obfuscate the code a little. It would be much better if we could declare type definitions of all arguments in one block.

I don’t want to cover Types in this article. You can bug my friend Gurjit to cover this in his articles as he’s the one who pointed this out to me.

3. Cause subtle bugs

Positional Arguments pack a lot of implicit behavior which can be the cause of subtle bugs. Let's see a common JS trick question

Tricky JavaScript question

Surprised? The reason for this weird output is hidden behind the implicit nature of positional arguments. You see the map and parseInt functions are hiding some of their secrets in plain sight.

Let’s review the code numbers.map(parseInt) again.

What exactly is happening here?

  • We run the map function on the numbers array.
  • map takes the first item of the array and passes it to parseInt.
  • Now, for the first item in the array (i.e. 1) it would do parseInt(1). Right...? Wrong!!!

Actually map passes three arguments to its callback function. The first is the current item in the array, the second is the index of the item. The third is the entire array. This in itself has no problem but the real issue is with the latter part.

numbers.map(parseInt) is not the same as numbers.map((item) => parseInt(item)). You could make the assumption that since the callback function just takes the item argument and pass it to parseInt we can skip the additional step. But the two are different: in the former, we pass all the data from map to parseInt whereas in the latter we only pass the item.

You might not know but there is a second argument of parseInt called the radix. By default, the value of radix is 10 (base 10 because we humans follow the decimal system for counting). What went wrong with the code was that we passed the index of the current item as the radix value to parseInt. These are the actual function calls that happened-

parseInt('1', 0, [...]);parseInt('4', 1, [...]);parseInt('8', 2, [...]);parseInt('10', 3, [...]);

Now that we know the issues, how can we do better?

Alternative to Positional Arguments

What if a function could tell by the name, what arguments it expects? That way even if you pass extra data to it by mistake it will only use the things it needs.

Let’s make our own wrapper over parseInt. Here’s a naive implementation.

Code for myCustomParseInt

myCustomParseInt accepts only one argument and that is an object. This object can have two keys– item & radix. Let’s use our custom util with map. It will be necessary to have an intermediate step to send args received by callback to myCustomParseInt.

Using myCustomParseInt with map

Notice that even if we pass the index to myCustomParseInt it won’t cause any problems. That’s because myCustomParseInt will just ignore it. This pattern of passing objects to functions is called named arguments. It is lot more explicit than positional arguments.

To change the radix we have to explicitly pass the radix key. That means if we want to parse a string with base 2 we have to go to the docs and see the exact name of parameter (radix). If we blindly pass any other key it won’t do anything. This is great for us because it avoids unintended behavior.

Named arguments with destructuring

A while back JavaScript got a feature called destructuring. Let’s use this in myCustomParseInt implementation.

Named arguments with destructuring

You’ll notice that just by adding two curly braces we get the benefits of named args but the ergonomics of positional args. You can think of destructuring as performing const item = objArgs.item;

If myCustomParseInt is called with undefined then JS would throw an error. That’s because undefined.item is not allowed. To avoid it we can add = {} in the end of destructuring. That way when we pass undefined it will now do {}.item which is valid JS. Here's the final implementation-

Final Implementation

With named arguments pattern we can also skip the arguments we don’t want to provide since the function no longer depends on the order in which the arguments are passed.

Named Args pattern make it easy to skip optional args

In conclusion I’d say named arguments is a powerful pattern and it has become very commonplace nowadays but you don’t always need to use them. Sometimes you could even combine the two. The fetch API in browser is used like this

Combine positional args with named args

Here the mandatory argument (API path) is a positional argument and then the optional params are accepted through named arguments.

Follow me on Twitter (https://twitter.com/dev__adi) if you have questions or if you want to stay up-to-date with cool developer tips and interesting technologies.

Learn More

--

--