211

When creating a JavaScript function with multiple arguments, I am always confronted with this choice: pass a list of arguments vs. pass an options object.

For example I am writing a function to map a nodeList to an array:

function map(nodeList, callback, thisObject, fromIndex, toIndex){
    ...
}

I could instead use this:

function map(options){
    ...
}

where options is an object:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

Which one is the recommended way? Are there guidelines for when to use one vs. the other?

[Update] There seems to be a consensus in favor of the options object, so I'd like to add a comment: one reason why I was tempted to use the list of arguments in my case was to have a behavior consistent with the JavaScript built in array.map method.

Christophe
  • 27,383
  • 28
  • 97
  • 140
  • 7
    The second option gives you named arguments, which is a nice thing in my opinion. – Werner Kvalem Vesterås Oct 10 '12 at 19:36
  • Are they optional or required arguments? – I Hate Lazy Oct 10 '12 at 19:37
  • @user1689607 in my example the last three are optional. – Christophe Oct 10 '12 at 19:40
  • Because your last two arguments are very similar, if the user passed only one or the other, you'd never really be able to know which one was intended. Because of that, you'd almost need named arguments. But I can appreciate that you'd want to maintain an API similar to the native API. – I Hate Lazy Oct 10 '12 at 19:51
  • 1
    Modeling after the native API isn't a bad thing, if your function does something similar. It all comes down to "what makes the code most readablae." `Array.prototype.map` has a simple API that shouldn't leave any semi-experienced coder puzzling over. – Jeremy J Starcher Oct 10 '12 at 19:53

10 Answers10

227

Like many of the others, I often prefer passing an options object to a function instead of passing a long list of parameters, but it really depends on the exact context.

I use code readability as the litmus test.

For instance, if I have this function call:

checkStringLength(inputStr, 10);

I think that code is quite readable the way it is and passing individual parameters is just fine.

On the other hand, there are functions with calls like this:

initiateTransferProtocol("http", false, 150, 90, null, true, 18);

Completely unreadable unless you do some research. On the other hand, this code reads well:

initiateTransferProtocol({
  "protocol": "http",
  "sync":      false,
  "delayBetweenRetries": 150,
  "randomVarianceBetweenRetries": 90,
  "retryCallback": null,
  "log": true,
  "maxRetries": 18
 });

It is more of an art than a science, but if I had to name rules of thumb:

Use an options parameter if:

  • You have more than four parameters
  • Any of the parameters are optional
  • You've ever had to look up the function to figure out what parameters it takes
  • If someone ever tries to strangle you while screaming "ARRRRRG!"
ravish.hacker
  • 1,189
  • 14
  • 21
Jeremy J Starcher
  • 23,369
  • 6
  • 54
  • 74
  • 17
    Great answer. It depends. Beware boolean traps http://ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html – Trevor Dixon Oct 10 '12 at 19:50
  • 2
    Oh yes... I had forgotten about that link. It really made me rethink how APIs work and I even rewrote several pieces of code after learning I did things dumb. Thanks! – Jeremy J Starcher Oct 10 '12 at 19:55
  • Not related much, but what about in languages such as C# where there are named parameters and it could look like `InitiateTransferProtocol(protocol: "http", sync: false, ....` ? – Earlz Oct 11 '12 at 02:10
  • @Earlz -- Never used a language with named parameters, but in concept it looked good to me. Sorry. – Jeremy J Starcher Oct 11 '12 at 14:24
  • 1
    'You've ever had to look up the function to figure out what parameters it takes' - Using an options object instead, won't you **always** have to look up the method to figure out that keys are needed and what they are called. Intellisense in the IDE doesn't surface this info, while params do. In most IDEs, you can simply hover mouse over the method and it'll show you what the params are. – simbolo Mar 27 '15 at 15:28
  • @simbolo - Depends on the language and environment. Not everyone uses full-blown IDEs. For those that use IDEs, they seldom get JavaScript Intellisense right. Visual Studio + Require.js, for instance, gives no support. Even within VS, I find that passing around query objects gives better code readability than having to hover over something. – Jeremy J Starcher Mar 27 '15 at 15:53
  • 1
    If y'all are interested in the performance consequences of this bit of 'coding best practices', here's a jsPerf testing both ways: http://jsperf.com/function-boolean-arguments-vs-options-object/10 Note the little twist in the third alternative, where I use a 'preset' (constant) options object, which can be done when you've many calls (during the lifetime of your runtime, e.g. your webpage) with the same settings, known at development time (in short: when your option values are kinda hardcoded in your sourcecode). – Ger Hobbelt Sep 20 '15 at 12:22
  • 1
    thanks for this: "It is more of an art that a science" and "I use code readability as the litmus test." and "If someone ever tries to strangle you while screaming "ARRRRRG!"" – Kristo Apr 21 '16 at 16:40
  • `initiateTransferProtocol("http", false, 150, 90, null, true, 18);` Ah, the good ol' days of the Win32 and COM: `CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_Isesoft, (void **)&pDispatch)` – Nick Oct 21 '16 at 15:14
  • @simbolo: You could always leverage ES6 arg destructuring: initiateTransferProtocol({ protocol, sync, ...rest }) { } If you go with the options object. – backdesk Mar 16 '17 at 11:06
  • @backdesk - While true, destructuring didn't exist when the answer was written – Jeremy J Starcher Mar 16 '17 at 13:55
  • @backdesk yeh I looked at the date 2015, I love destructing and use it all the time now. Perhaps the answer should reflect this? – simbolo Mar 17 '17 at 09:33
  • Does the fact that objects work as reference vs copies play into this scenario at all? – Sean Nov 06 '18 at 15:13
  • 2
    @Sean Honestly, I no longer use this style of coding at all. I've switched over to TypeScript and use of named parameters. – Jeremy J Starcher Nov 06 '18 at 15:52
  • 1
    if you're using `typescript` you can make the object readonly (of course this is only a build time error) but its unlikely a regular person would try to work their way around this – Clifford Fajardo Oct 23 '21 at 20:56
39

Multiple arguments are mostly for obligatory parameters. There's nothing wrong with them.

If you have optional parameters, it gets complicated. If one of them relies on the others, so that they have a certain order (e.g. the fourth one needs the third one), you still should use multiple arguments. Nearly all native EcmaScript and DOM-methods work like this. A good example is the open method of XMLHTTPrequests, where the last 3 arguments are optional - the rule is like "no password without a user" (see also MDN docs).

Option objects come in handy in two cases:

  • You've got so many parameters that it gets confusing: The "naming" will help you, you don't have to worry about the order of them (especially if they may change)
  • You've got optional parameters. The objects are very flexible, and without any ordering you just pass the things you need and nothing else (or undefineds).

In your case, I'd recommend map(nodeList, callback, options). nodelist and callback are required, the other three arguments come in only occasionally and have reasonable defaults.

Another example is JSON.stringify. You might want to use the space parameter without passing a replacer function - then you have to call …, null, 4). An arguments object might have been better, although its not really reasonable for only 2 parameters.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • +1 same question as @trevor-dixon: have you seen this mix used in practice, for example in js libraries? – Christophe Oct 11 '12 at 05:56
  • An example could be jQuery's [ajax methods](http://api.jquery.com/jQuery.ajax/). They accept the [obligatory] URL as the first argument, and a huge options argument as the second. – Bergi Oct 11 '12 at 10:42
  • so weird! I've never noticed this before. I've always seen it used with the url as an option property... – Christophe Oct 11 '12 at 15:59
  • Yes, jQuery does weird thing with its optional parameters while staying backwards compatible :-) – Bergi Oct 11 '12 at 16:07
  • 1
    In my opinion, this is the only sane answer here. – Benjamin Gruenbaum Jan 28 '14 at 17:52
14

Using the 'options as an object' approach is going to be best. You don't have to worry about the order of the properties and there's more flexibility in what data gets passed (optional parameters for example)

Creating an object also means the options could be easily used on multiple functions:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

function1(options){
    alert(options.nodeList);
}

function2(options){
    alert(options.fromIndex);
}
Fluidbyte
  • 5,162
  • 9
  • 47
  • 76
  • The (reasonable) assumption here is that the object will always have the same key pairs. If you're working with a crap/inconsistent API then you have a different problem on your hands. – backdesk Mar 16 '17 at 11:05
12

It can be good to use both. If your function has one or two required parameters and a bunch of optional ones, make the first two parameters required and the third an optional options hash.

In your example, I'd do map(nodeList, callback, options). Nodelist and callback are required, it's fairly easy to tell what's happening just by reading a call to it, and it's like existing map functions. Any other options can be passed as an optional third parameter.

Trevor Dixon
  • 23,216
  • 12
  • 72
  • 109
9

I may be a little late to the party with this response, but I was searching for other developers' opinions on this very topic and came across this thread.

I very much disagree with most of the responders, and side with the 'multiple arguments' approach. My main argument being that it discourages other anti-patterns like "mutating and returning the param object", or "passing the same param object on to other functions". I've worked in codebases which have extensively abused this anti-pattern, and debugging code which does this quickly becomes impossible. I think this is a very Javascript-specific rule of thumb, since Javascript is not strongly typed and allows for such arbitrarily structured objects.

My personal opinion is that developers should be explicit when calling functions, avoid passing around redundant data and avoid modify-by-reference. It's not that this patterns precludes writing concise, correct code. I just feel it makes it much easier for your project to fall into bad development practices.

Consider the following terrible code:

function main() {
    const x = foo({
        param1: "something",
        param2: "something else",
        param3: "more variables"
    });

    return x;
}

function foo(params) {
    params.param1 = "Something new";
    bar(params);
    return params;
}


function bar(params) {
    params.param2 = "Something else entirely";
    const y = baz(params);
    return params.param2;
}

function baz(params) {
    params.params3 = "Changed my mind";
    return params;
}

Not only does this kind of require more explicit documentation to specify intent, but it also leaves room for vague errors. What if a developer modifies param1 in bar()? How long do you think it would take looking through a codebase of sufficident size to catch this? Admittedly, this is example is slightly disingenuous because it assumes developers have already committed several anti-patterns by this point. But it shows how passing objects containing parameters allows greater room for error and ambiguity, requiring a greater degree of conscientiousness and observance of const correctness.

Just my two-cents on the issue!

ajxs
  • 3,347
  • 2
  • 18
  • 33
7

Your comment on the question:

in my example the last three are optional.

So why not do this? (Note: This is fairly raw Javascript. Normally I'd use a default hash and update it with the options passed in by using Object.extend or JQuery.extend or similar..)

function map(nodeList, callback, options) {
   options = options || {};
   var thisObject = options.thisObject || {};
   var fromIndex = options.fromIndex || 0;
   var toIndex = options.toIndex || 0;
}

So, now since it's now much more obvious what's optional and what's not, all of these are valid uses of the function:

map(nodeList, callback);
map(nodeList, callback, {});
map(nodeList, callback, null);
map(nodeList, callback, {
   thisObject: {some: 'object'},
});
map(nodeList, callback, {
   toIndex: 100,
});
map(nodeList, callback, {
   thisObject: {some: 'object'},
   fromIndex: 0,
   toIndex: 100,
});
Izkata
  • 8,961
  • 2
  • 40
  • 50
4

Object is more preferable, because if you pass an object its easy to extend number of properties in that objects and you don't have to watch for order in which your arguments has been passed.

happyCoda
  • 418
  • 2
  • 2
4

It depends.

Based on my observation on those popular libraries design, here are the scenarios we should use option object:

  • The parameter list is long (>4).
  • Some or all parameters are optional and they don’t rely on a certain order.
  • The parameter list might grow in future API update.
  • The API will be called from other code and the API name is not clear enough to tell the parameters’ meaning. So it might need strong parameter name for readability.

And scenarios to use parameter list:

  • Parameter list is short (<= 4).
  • Most of or all of the parameters are required.
  • Optional parameters are in a certain order. (i.e.: $.get )
  • Easy to tell the parameters meaning by API name.
Lynn Ning
  • 71
  • 3
1

For a function that usually uses some predefined arguments you would better use option object. The opposite example will be something like a function that is getting infinite number of arguments like: setCSS({height:100},{width:200},{background:"#000"}).

Ilya Sidorovich
  • 1,530
  • 1
  • 13
  • 15
-1

I would look at large javascript projects.

Things like google map you will frequently see that instantiated objects require an object but functions require parameters. I would think this has to do with OPTION argumemnts.

If you need default arguments or optional arguments an object would probably be better because it is more flexible. But if you don't normal functional arguments are more explicit.

Javascript has an arguments object too. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

dm03514
  • 54,664
  • 18
  • 108
  • 145