Date published: 04.06.2021
Table of Contents

How to set default parameter values in JavaScript functions

When creating a function we often want to make sure that the values (called arguments) passed to the function when called has default values in case the user does not provide one, or provides the wrong one.

There are many ways to set default values, but here are five common ways:

  1. If statement
  2. Tenary expression
  3. Short-circuit evaluation
  4. Object.assign
  5. ECMAScript 2015 (ES2015) default parameters

When we call a function without passing a value for one of the parameters (the names used when declaring a function) JavaScript will instead pass the value undefined as a default value for this parameter.

The three first methods overrides the JS default value with our own default value by detecting this undefined value inside the function, and instead replacing it with a new value, while the last two sets their own default values initially instead of relying on the JS undefined.


If statement (method 1)

The first and perhaps most intuitive way to set default values is with a simple if statement.

The syntax should be familiar.

if (test expression)
  statement

Using this we can set a default value for a variable like so:

function increment(num) {
  if (!num) { num = 0 }
  return ++num
}
increment()   // returns 1 since "num" is set to 0 before incrementation
increment(22) // returns 23

If no value is passed for the num parameter, num will be set to the number 0.

If the value of num is set to undefined if no value is passed for num, then why do we write if(!num) instead of if (num === undefined)? The answer is that they both work, its just that !num is shorter and faster to write, so we prefer it for this reason. This does come at a cost though. num === undefined would only allow the value undefined to enter the if, setting the default value only in the case the value is undefined, while !num allows any value that is seen as false (called falsy value) by JS to enter the if and set the default value. This makes the value undefined enter the if, but also make values like 0, "", NaN enter the if and set default value, since they are all also seen as falsy values. If we do not want these values to be replaced by a default value, we must use num === undefined instead.

Tip: If you want to know which values are seen as false (read: converted too) by JS, you can do so with either !!value or Boolean(value).

Boolean(20)         // true
!!20                // true

Boolean(0)          // false
!!0                 // false

We can make the code a bit less verbose by skipping the brackets.

function increment(num) {
  if (!num) num = 0
  return ++num
}

This works because brackets are only necessary when we have several statements inside the if statement. Brackets denote a block statement - a statement used to group other statements.


Tenary expression (method 2)

The second method, a tenary expression, can be used to set default values the same way as an if statement.

The syntax of a tenary expression looks like this:

test expression ? value expression : value expression

If the test expression evaluates to true, the lefthand side of the semicolon is returned, else the righthand side value is returned.

The if statement example we used above can be re-written as a tenary expression instead.

function increment(num) {
  num = num ? num : 0
  return ++num
}

If we pass a value for num (a value not seen as falsy), num will be assigned itself, else num will be assigned 0.

In this way, the left side of : equals the if statement and the right side equals the else statement in an if else statement.

To better exemplify this relationship: this tenary expression and if statement is identical in behaviour.

num = num ? num : 0
if (num) {
  num = num
}
else {
  num = 0
}

Tenary expressions do not create less code than an if statement. However, when used as a replacement for an if else statement, it creates shorter syntax, and also fits a single line better.

The bottom line is that an if statement can achieve everything a tenary expression can, and the choice between the two mostly comes down to which feels more eloquent in the given situation and the preference of the programmer. For this reason, you will encounter tenary expressions used, so its necessary to know them even if you don't use them personally.


Short-circuit evaluation (method 3)

A third method for assigning default values to parameters is called short-circuit evaluation. They are expressions , just like tenary expressions. These expressions make use of the || operator and how this operator is seen and evaluated by JavaScript.

The syntax for such an expression is simple and looks like this:

value expression || value expression

To use the || operator to set default values we can do as in the following code snippet.

function increment(num) {
  num = num || 0
  return ++num
}

Just like in the previous examples, this assigns the default value 0 to num if num is undefined (or any value seen as falsy).

How it works

First, JS converts the value on the lefthand side to a boolean. If the result of this conversion is the boolean true JS returns the value on the left. If, however, the result of this conversion is the Boolean false, it returns the value on the righthand side instead.

From this we gather two important points:

  1. The expression does not return a bool. It returns the value on either the left or right side. The boolean is only used for evaluation purposes.
  2. The result is always just one of the values of either side

The name short-circuit comes from the second point. If the lefthand side converts to true the expression "short-circuits", meaning it never goes to the right side.

Note: Using the && operator also works for selectively returning one of the sides. However, it does not work for assigning default values. This is because the righthand side (the default) value would be applied when the argument is true, not false, meaning everytime the argument is actually passed, not when it's not passed.

A flaw

It's tempting to use short-circuit evaluation due to its very short and easy syntax. Unfortunately, though, the reason for this short syntax, is also the cause of a problem. In a short-circuit expression the test and return value is intrinsically tied together.

both test expression and returned value || returned value

By contrast, in a tenary expression, we can see that these are separate, and that the value that the test expression evaluates to, does not affect the value returned.

test expression ? returned value : returned value

The consequence of this tying together is that we cannot change the lefthand side from e.g. arg || 0 to arg === "number" || 0, since this would return a boolean if true instead of the value stored in arg. In other words, the argument test and the returned value needs to be the same.

So in short. Short-circuit expressions are only useful when we want to assign the argument itself or a default value. Not, for instance, when we want to check if the value is a specific value (arg === undefined) or of a specific datatype (typeof arg === "number"), before choosing to assign a default value. In that case we need to use another method, like so: typeof arg === "number" ? arg : 0.


Object.assign (method 4)

Object.assign is a function that exists in many environments (e.g. chrome, firefox, node.js).

Object.assign(targetObject, sourceObject1, sourceObject2, etc..)

It assigns the properties of one or many objects (source objects), to a target object.

How it works

What rules does the function follow when assigning to the target object?

In short what happens is that:

  • If the property exists in the target object and source object, it replaces the value of the target object property.
  • If the property exists in the source object only, it creates/adds it, with the value, to the target.
  • If the property does not exists in source, we dont do anything.

The following code illustrates all of these rules in effect:

  // create the object we want to assign TO
  var target = {name: "Ben", age: 29}
  // create the object we want to assign FROM
  var source = {name: "Hans", lastname: "Gruber"}
  // assign the properties of source to target
  Object.assign(target, source)


  // This changes the target object from
  // {
  //   name: "Ben",
  //   age: 29
  // }
  //
  // to:
  // {
  //   name: "Hans",           // same key, but new value
  //   age: 29,                // same key and value
  //   lastname: "Gruber"      // a new property entirely
  // }

As you might have noticed, if the user provides an (source/config) object with properties not present in the target object those properties are added to the defualt object as well, however, this does not cause an issue, since any unnecessary properties added is not referenced anywhere in the function, causing them to just be treated like they dont exists.

Setting default values with Object.assign

A common use-case for assign is when we want to set default configuration. If a function executes a process and we want to customize how this process is run, we create a default object in the function which controls this process and then we override any values of the default config object with the user provided object.

We can illustrate the use of Object.assign to set default values by creating a tooltip that is displayed a certain way, but lets the user (function caller) have the final say in how it's displayed.

function showTooltip(user_config) {
  // create values that will be used by default if no values are passed
  var default_config = {
    direction: "topleft",
    backgroundColor: "#000"
  }

  // override some of the values in the default_config object
  // with user preferences (user_config)
  Object.assign(default_config, user_config)

  // some imaginary function that displays a tooltip using the
  // values in default_config
  displayTooltip(default_config)
}

// shows a tooltip, using the default direction ("topleft"),
// but user preferred background color ("#ddd")
showTooltip({backgroundColor: "#ddd"})

The difference with this method compared to the others, is that we need to predefine default values from the get go, and then we replace the values if need be. In the other methods we only applied the new values if the user did not provide a value, or a wrong value. Using this method we assume values will not be passed, in the others we assumed they will.

Your own version

Using Object.assign depends on whether it exists in the environment that you are using (chrome, node etc), and the environments version (10, 11 etc).

If Object.assign is not available you can make your own:

function assign(target, varArgs) {
  var obj = Object(target)

  for (var index = 1; index < arguments.length; index++) {
    var nextSource = arguments[index];

    if (nextSource !== null && nextSource !== undefined) {
      for (var nextKey in nextSource) {
        if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
         obj[nextKey] = nextSource[nextKey];
        }
      }
    }
  }

  return obj
}

This is a simplified version of Object.assign that does not emulate its exact behavior, but does the trick most of the time. If you want a more elaborate version that behaves identically to Object.assign, you can copy the function found here into your own code.

Note: To create a function with the exact same behaviour as Object.assign (called a polyfill) it's necessary to use another function called Object.defineProperty. This function does not exists in some older browsers (and cannot be polyfilled itself), this means that, even though the linked function is superior to the simplified function in that it has identical behaviour to Object.assign, it works in less browsers than the aforementioned. This is not really a big deal though since the support is still pretty good, and the simplified version does not work in all version either, even though it has better support.


ECMAScript 2015 (ES2015) default parameters (method 5)

Since the JavaScript version released in 2015 another method for setting default parameters values have existed simply named "default parameters".

The previous methods set default values inside the function. They let JavaScript pass undefined as the default value to the function, and then we used an if statement, tenary or short-circuit expression to override the undefined value to whatever we wanted instead.

With default parameters, however, we decide what value will be passed instead of undefined, so we dont need to override the value inside the function.

The syntax for setting default parameter values looks like this:

function fn(param1 = value expression, param2 = value expression, etc...) {
  // ... code
}

Using this syntax we can set default values for parameters like this:

function add(a = 0, b = 10 - 10) {
  return a + b
}

If nothing is passed for either a or b (or the value passed is undefined), both a and b will be set to the number 0 (b = 10 - 10 = 0), resulting in return 0 + 0.

Default values can also be stored in variables:

var age = 20
function increment(num = age) {
  return ++num
}
increment() // returns the number 20

In summary, using this more modern method is a very easy way to set default values. However, it isn't that commonly encountered; due to a combination of less browser support, slow adoptation by developers and the fact that much of the JavaScript code you encounter is already transpiled by babel (transformed into an older JavaScript version for better browser support) before you read it.


Conclusion

We have explored five ways to set default function values if the function caller does not provide, or provides the wrong, value.

Each method has its own strengths and weaknesses. For instance, using an if statement would offer the best readability, but the short-circuit method would produce the shortest syntax - tempting for when you set default values often, but insufficient if you need to explicitly check what the passed value is before choosing to assign a default value.

In the end though, which one you choose is mostly down to preference, since each option will do the job in most scenarios.