Designing APIs: use arrays instead of rest parameters

Or why _.get(object, ['deep', 'prop']) is better than _.get(object, 'deep', 'prop').

Imagine you’re designing a function that accepts a variable number of arguments. Like getDeepProperty():

function getDeepProperty(<object> and <chain of nested fields>) {
  // Retrieve the deep property and return it
}

And you’re facing a choice: should you pass the chain of fields directly:

function getDeepProperty(object, ...fields) { /* ... */ }

getDeepProperty(someData, 'i', 'love', 'designing', 'apis');

or as an array:

function getDeepProperty(object, fields) { /* ... */ }

getDeepProperty(someData, ['i', 'love', 'designing', 'apis']);

Earlier, I preferred the former way because it seemed “cleaner”. Now, I use the latter one – because it makes the function forward-compatible.

At some moment in the future, you might want to pass an additional parameter specifying the default return value – i.e., what the function should return if the deep property is absent. The only backwards-compatible way to do this is to pass this parameter as the last argument to the function, like this:

function getDeepProperty(
  <object>,
  <chain of fields>,
  <default return value>
) {
  // Retrieve the deep property and return it,
  // or return the default value
}

And here comes the difference. If your function accepts an array, you just append a new argument, do a switch (arguments.length) inside your function body, and the old code keeps working:

function getDeepProperty(...args) {
  if (args.length === 3) {
    const [object, fields, defaultValue] = args;
    // Execute the new behavior
  } else {
    const [object, fields] = args;
    // Execute the old behavior
  }
}

// The new API calls work great: a user passes three arguments,
// the function understands this and returns 'no'
getDeepProperty({}, ['i', 'love', 'designing', 'apis'], 'no');

// The old API calls keep working: a user passes two arguments,
// the function works as usual and e.g. throws an exception
getDeepProperty({}, ['i', 'love', 'designing', 'apis']);

But if your function accepts a variable number of arguments, you become unable to add an argument without breaking the old API users. The function can’t understand whether the last parameter is the default return value or a value in the chain of fields:

function getDeepProperty(object, ...fieldsAndDefaultValue) {
  // So, is fieldsAndDefaultValue[fieldsAndDefaultValue.length - 1]
  // a default value or just a field in a chain of fields?
  // You can’t know

  // Execute the new behavior
}

// The new API works great: the function returns 'no'
getDeepProperty({}, 'i', 'love', 'designing', 'apis', 'no');

// But the old API breaks: the function returns 'apis'
getDeepProperty({}, 'i', 'love', 'designing', 'apis');

So, here’s the rule:

When defining a function, prefer arrays over variable number of arguments

Author: Ivan Akulov

I'm a software engineer specializing in web performance, JavaScript, and React. I’m also a Google Developer Expert. I work at Framer.

One thought on “Designing APIs: use arrays instead of rest parameters”

Comments are closed.