First Class Functions in JavaScript

by Gautham Pai

Concept of First Class Citizens

What is a first class citizen of a programming language?

An entity is called a first class citizen if it satisfies these 3 conditions:

  1. We should be able to assign that entity to a variable
  2. We should be able to pass it to a function
  3. We should be able to return it from a function

Let us consider some common examples.

Numbers in a language are first class because they satisfy the above 3 conditions.

  1. We can assign numbers to variables.
let a = 10;
  1. We can pass a number to a function.
function foo(x) {
	// do something with x
}

foo(10);
  1. We can return a number from a function.
function foo() {
	return 10;
}

let x = foo();

In JavaScript, functions are also first class! This means:

  1. We can assign a function to a variable
  2. We can pass a function to a function
  3. We can return a function from a function

Beginners may find this a bit hard to understand. So let us take up some examples and understand this concept better.

Assigning a Function to a Variable

Consider the following:

var x = 5;

What does the above do?

  1. We create a value 5 in memory.
  2. We create a variable x.
  3. We make the variable x refer to (or point to) the value 5.

We can imagine that the variable x is in one location and the value 5 is in another and x is containing the address of the location 5.

Variable and value

What happens when we assign a new value to x?

var x = 5;
x = "Hello";

Variable is assigned to a new value

A new value Hello is created in a different location and the same variable x is now made to refer to this new value.

What happens to the value 5? Generally speaking, values that are no longer referred to by any variable will be discarded. In other words, the memory occupied by such values will be reclaimed. (There are some optimizations that JavaScript engines implement, so strictly speaking, this may not be true for values like 5, but you can ignore that for now.)

Let us consider one more example:

var x = 5;
x = x + 5;

Incrementing a number

A new value 10 is created in a new location and x now refers to this location. Note that in JavaScript, numbers and strings are immutable, i.e. they can only be created, the values cannot be modified in-place once created.

Now consider this example:

var x = 5;
var y = x;

Two variables referring to the same value

When we say var y = x, we mean that y must refer to the same value as what x is referring to.

With this understanding, let us now look at functions in JavaScript.

What happens when we create a function in JavaScript?

Consider the following example:

function sum(x, y) {
  return x + y;
}

3 things happen:

  1. An object containing the function definition is created in memory. Here, when I say object, think of it as something that occupies space in memory. We will call this "function object" in future.
  2. A variable by the name sum is created.
  3. The variable is made to refer to the function object.

Function Definition

This is not very different from what happens when we say var x = 5 isn't it?

Note that sum here is also a variable. This variable has no special significance.

In fact, it's perfectly valid to write this:

var sum = function sum(x, y) {
  return x + y;
}
sum(2, 3);

Expanded Function Definition

The above statement has 2 references to the word sum. One is a variable and another comes as part of the function. The second occurrence is called a function name.

Which sum are we refering to when we call:

sum(2, 3)

Is it the variable sum or the function name sum?

To understand this, consider the following example:

var x = 5;
console.log(x);

We are using the variable name, x to refer to the value 5 isn't it?

Similarly, it's the variable name sum that we refer to when calling the function sum(2, 3), not the function name. This is clear from the following example:

Different Variable and Function Names

var foo = function bar(x, y) {
  return x + y;
}
> bar(2, 3)
Uncaught ReferenceError: bar is not defined
> foo(2, 3)
5

If that's the case, why have the function name at all? Can we omit it? Let's consider this example:

Anonymous Functions

var sum = function(x, y) {
    return x + y;
}
sum(2, 3)

It works! So we can define functions without a name and it works fine. Such functions (that don't have a name) are called anonymous functions.

Why would we ever need to name a function?

Naming a function is useful if we intend to refer to the function name inside the function (example, in recursive functions).

The name of the function becomes available in the function scope when the function is called.

Function Name Refered in Function

var bar = function foo() {
  console.log(foo);
}
> bar()
[Function: foo]

Just like how numbers, strings, etc are types, a function object is another type in JavaScript.

> var sum = function(x, y) { return x + y; }
undefined
> typeof(sum)
'function'

Note that when we say type(sum), we are asking what is the type of the value that the variable sum refers to. The variable sum has no special significance because it is referring to a function. We can use the same variable to refer to a value of a different type.

In the following example, we are making the sum variable refer to a number:

> sum = 10
10
> typeof(sum)
'number'
> 

What happens if we now "call" sum as if it were a function?

> sum(2, 3)
Uncaught TypeError: sum is not a function

JavaScript is telling us that the variable sum is not referring to an object of type function; therefore it doesn't make sense to "call" it.

When a variable refers to an object of type function, then we can call it:

> var sum = function(x, y) { return x + y;}
undefined
> typeof(sum)
'function'
> sum(2, 3)
5

What happens if we assign add = sum?

Remember that when we say variable2 = variable1, we are saying that we want variable2 to refer to the same object as what variable1 is referring to.

So when we say add = sum, we are making the variable add refer to the same function object as what sum is referring to.

> var add = sum;
undefined

Variable Assigned Function

What is the type of add?

Since add is referring to the same function object, its type is obviously function. Therefore we must be able to call it.

Calling a function essentially means that we want to execute the function definition present in the function object.

> typeof(add)
'function'
> add(2, 3)
5
> 

What happens if sum now refers to something else?

> sum = 10
10

sum no longer refers to function

Can we still call add(x, y)?

Since add still refers to the function object, we must be able to call it:

> add(2, 3)
5
> 

And there we have it! We have assigned a function (sum) to a variable add. This covers the first case of first class functions!

Also, to be clear, there is a difference between:

let add = sum;

v/s

let add = sum(2, 3);
  • add = sum is a case of first-class functions. It means let add refer to the same object as sum.
  • add = sum(2, 3) is about assigning the return value of sum to the variable add. This is NOT a case of first-class functions.

Passing a Function to a Function

Study the following code carefully:

function fooCaller(foo) {
  foo();
}

function bar() {
  console.log('Bar');
} 

fooCaller(bar);

Let us break this down:

function fooCaller(foo) {
  foo();
}

What happens after execution of the above 3 lines?

  1. The function object fooCaller is created.
  2. The variable fooCaller is created.
  3. fooCaller refers to the function object.

Does foo get created?

No! The arguments of a function, like foo are created only when the function is called. It is important to note the difference between a function call and a function definition. We have only defined the function fooCaller so far. We have not yet called it. So neither is foo created nor does the body of the fooCaller get executed.

Let us now consider the next 3 lines:

function bar() {
  console.log('Bar');
}

What happens when the above 3 lines are executed?

  1. The function object bar is created.
  2. The variable bar is created.
  3. The variable bar refers to the function object.

bar is not called yet (this may seem obvious but I have seen that beginners sometimes start forgetting basics of functions when learning first class functions 😃).

Now comes the interesting line:

fooCaller(bar);

What happens when this line is executed?

  1. fooCaller is called.
  2. bar is passed to fooCaller and it is received as foo. This means, foo is created and is assigned bar. This means, foo now refers to what bar is referring to, i.e. the function `bar.
  3. foo is called, which means we execute the body of the function object that foo is referring to, which is nothing but the bar function.
  4. This prints Bar in the console.

What is the use of this? We could called bar and that would print Bar as well. Why pass it to fooCaller and get fooCaller to call it?

To appreciate this, let us consider some applications of first-class functions.

Applications of First Class Functions

Before we answer the question about why we need these concepts, let us consider some basics.

What does a computer do? A computer does: Input -> Process -> Output

What do we do in a programming language? We define how data is brought into RAM (Input), how we process it (Process) and what to do with the result (Output).

When data is in RAM, we give it a meaning - in the form of data types. First, we have primitive data types and operations on them. Then we have composite data types and operations on them.

Consider the following example:

Given a list of person objects, give me a list of their names.

> let people = [{name: 'John', email: '[email protected]'}, {name: 'George', email: '[email protected]'}]
undefined
> let peopleNames = []
undefined
> for(let i = 0; i < people.length; i++) {
... peopleNames.push(people[i].name);
... }
2
> peopleNames
[ 'John', 'George' ]
> 

We do a lot of similar "data processing" operations. What if we had better, concise constructs to work with structures like these?

That's what we can do with application of some functional programming concepts.

There are several patterns of data transforms that have been identified. And there are some simple operations in functional languages to apply these data transforms. Let us look at a few of them.

Applications of Passing Functions to Functions

map Function

A very common operation we perform when working with data is, "given a collection of n items, get me a corresponding collection of n other items", where the input and output items have some relationship.

For example, given a list of numbers, give me a list of the squares of the numbers.

map

How can we do this (without first-class functions)?

function getSquares(inputNums) {
  let squaresList = [];
  for(let num in inputNums) {
    squaresList.push(inputNums[num] * inputNums[num]);
  }
  return squaresList;
}

let nums = [1, 2, 3];
let squares = getSquares(nums);
console.log(squares); // [1, 4, 9]

What if we want cubes?

I have seen learners saying, "Same thing, except do inputNums[num] * inputNums[num] * inputNums[num]". How we wish, we could just tell a computer, "Same thing" and it could understand us!

But, in the approach we have used, we will have to write the whole code:

function getCubes(inputNums) {
  let cubesList = [];
  for(let num in inputNums) {
    cubesList.push(inputNums[num] * inputNums[num] * inputNums[num]);
  }
  return cubesList;
}

let nums = [1, 2, 3];
let cubes = getCubes(nums);
console.log(cubes); // [1, 8, 27]

Wouldn't it be nice if we could abstract this out?

We do see a pattern in the getSquares and getCubes functions. Both are taking an input list and operating on each item of the input list and the result of the operation is being added to an output list, which is then returned.

What if we could abstract the common functionality into a function and somehow pass only the unique functionality of computing square or cube to this common function?

This is exactly what a map does! The code may look like this:

function map(inputList, f) {
  var outputList = [];
  for(var index in inputList) {
    outputList.push(f(inputList[index]));
  }
  return outputList;
}

var nums = [1, 2, 3];
function square(x) {
  return x * x;
}
function cube(x) {
  return x * x * x;
}
console.log(map(nums, square)); // [1, 4, 9]
console.log(map(nums, cube));   // [1, 8, 27]

We have now written a generic function that can be used to convert a n-item input list to a n-item output list.

We can simplify this further by defining the function passed to map on the fly during the invocation of map:

function map(inputList, func) {
  var outputList = [];
  for(var index in inputList) {
    outputList.push(func(inputList[index]));
  }
  return outputList;
}

var nums = [1, 2, 3];
console.log(map(nums, function square(x) {
  return x * x;
}));
console.log(map(nums, function cube(x) {
  return x * x * x;
}));

We can make functions anonymous:

function map(inputList, func) {
  var outputList = [];
  for(var index in inputList) {
    outputList.push(func(inputList[index]));
  }
  return outputList;
}

var nums = [1, 2, 3];
console.log(map(nums, function(x) {
  return x * x;
}));
console.log(map(nums, function(x) {
  return x * x * x;
}));

We can even convert them to arrow functions:

function map(inputList, func) {
  var outputList = [];
  for(var index in inputList) {
    outputList.push(func(inputList[index]));
  }
  return outputList;
}

var nums = [1, 2, 3];
console.log(map(nums, (x) => {
  return x * x;
}));
console.log(map(nums, (x) => {
  return x * x * x;
}));

If the function body of the arrow function is a single statement, we don't need curly braces or the return keyword:

function map(inputList, func) {
  var outputList = [];
  for (var index in inputList) {
    outputList.push(func(inputList[index]));
  }
  return outputList;
}

var nums = [1, 2, 3];
console.log(map(nums, x => x * x));
console.log(map(nums, x => x * x * x));

In fact, we don't even need to define our own map function. There is already a built-in map available to be applied on arrays:

var nums = [1, 2, 3];
console.log(nums.map(x => x * x));
console.log(nums.map(x => x * x * x));

Let us try this on a few more examples.

Given a list of strings (which are numeric) convert it to a list of numbers. Would this be a case of a map?

  1. We are converting n-inputs to n-outputs.
  2. There is a relationship between one input item and its corresponding output item. Eg: The string '199' would become the number 199 in the output.

How do we convert one input to one output? Given a string, how can we convert it to a number?

let inputString = '199'
let outputNumber = Number(s)
console.log(outputNumber); // 199

Once we know how to convert one input to one output, we can then pass the function to a map to be applied on all inputs as follows:

var nums = ['199', '23', '34', '566'];
console.log(nums.map(s => Number(s)));

Another example:

Given a list of people, get me their names.

var people = [
  { name: 'John', email: '[email protected]', location: 'London' },
  { name: 'George', email: '[email protected]', location: 'Paris' },
  { name: 'David', email: '[email protected]', location: 'New York' }
];

function getNameFromPersonObject(person) {
  return person.name;
}
console.log(people.map(getNameFromPersonObject));

Or even better with arrow functions:

console.log(people.map(person => person.name));

Given a list of people, generate an email addresses for the people based on their names. The email is their name in lower case + '@example.com'.

var people = [
  { name: 'John', location: 'London' },
  { name: 'George', location: 'Paris' },
  { name: 'David', location: 'New York' }
];

console.log(
  people.map(
    person => person.name.toLowerCase() + '@example.com'
  )
);

Given a list of people with 2 fields (name and location), compute an email field using the name as before, and return a list of people with 3 fields (name, email and location).

var people = [
  { name: 'John', location: 'London' },
  { name: 'George', location: 'Paris' },
  { name: 'David', location: 'New York' }
];

console.log(
  people.map(person => {
    return {
      name: person.name,
      email: person.name.toLowerCase() + '@example.com',
      location: person.location
    };
  })
);

If we don't want to use return in our arrow function, we can use () to indicate that this is a single expression that needs to be returned.

var people = [
  { name: 'John', location: 'London' },
  { name: 'George', location: 'Paris' },
  { name: 'David', location: 'New York' }
];

console.log(
  people.map(person => ({
    name: person.name,
    email: person.name.toLowerCase() + '@example.com',
    location: person.location
  })
  )
);

We can use map to get a subset of the fields, to add new fields, to modify one or more fields and get a new list based on the input list. Let us explore some more examples:

Using object destructuring syntax to add fields:

var people = [
  { name: 'John', location: 'London' },
  { name: 'George', location: 'Paris' },
  { name: 'David', location: 'New York' }
];

console.log(
  people.map(
    ({name, location}) => ({
      name,
      email: name.toLowerCase() + '@example.com',
      location 
    })
  )
);

If order is not important, we can even use the following syntax:

var people = [
  { name: 'John', location: 'London' },
  { name: 'George', location: 'Paris' },
  { name: 'David', location: 'New York' }
];

console.log(
  people.map(
    person => ({
      ...person,
      email: person.name.toLowerCase() + '@example.com'
    })
  )
);

Using object destructuring to remove a field from a set of fields:

var people = [
  { name: 'John', age: 20, location: 'London' },
  { name: 'George', age: 30, location: 'Paris' },
  { name: 'David', age: 40, location: 'New York' }
];

function getPersonWithoutAge({age, ...rest}) {
  return rest;
}

console.log(people.map(getPersonWithoutAge));

Or with arrow:

const people = [
  { name: 'John', age: 20, location: 'London' },
  { name: 'George', age: 30, location: 'Paris' },
  { name: 'David', age: 40, location: 'New York' }
];

const getPersonWithoutAge = ({ age, ...rest }) => rest;

console.log(people.map(getPersonWithoutAge));

A few more examples:

// Given a list of objects, get me a list of objects with one of the fields
console.log(people.map(({name}) => name));
// Given a list of objects, get me a list of objects with a subset of the fields
console.log(people.map(({name, age}) => ({name, age})));
// Given a list of objects, get me a list of objects with some fields removed
console.log(people.map(({age, ...rest}) => rest));
// Given a list of objects, get me a list of objects with some fields updated
console.log(people.map(({ age, ...rest }) => ({ age: age + 1, ...rest })));
// Another example
var contacts = ['Gautham,Bangalore', 'Jatin,Mumbai'];
var contact_objs = contacts.map(contact => {
    var [name, place] = contact.split(',');
    return {name, place};
});
console.log(contact_objs);

filter Function

A filter is used when we have n inputs and want a subset of n as output based on a condition.

Let us say we have a list of numbers and want a list of the even or odd numbers.

The function passed to filter works by taking one input item at a time and returning either true if the condition is satisfied or false if the condition is not satisifed.

var nums = [1, 2, 3, 4, 5];

function filter(inputList, func) {
  var outputList = [];
  for (var index in inputList) {
    if (func(inputList[index])) {
      outputList.push(inputList[index]);
    }
  }
  return outputList;
}

function isOdd(num) {
  return num % 2 == 1;
}

function isEven(num) {
  return num % 2 == 0;
}

console.log(filter(nums, isOdd));
console.log(filter(nums, isEven));

Let us consider some practical applications of filter.

Given a list of people, get me the list of people from London.

var people = [
  { name: 'John', age: 20, location: 'London' },
  { name: 'George', age: 30, location: 'Paris' },
  { name: 'David', age: 40, location: 'London' }
];

We start by defining a function that works on one object, that returns true/false based on the condition (is the person from "London"):

function isPersonFromLondon(person) {
  return person.location === 'London';
}

Now pass this function to the filter:

console.log(people.filter(isPersonFromLondon));

Or with arrow function:

console.log(people.filter(person => person.location === 'London'));

A few more examples:

// People with age >= 30
console.log(people.filter(person => person.age >= 30));
// People from London who are aged >= 30
console.log(people.filter(person => person.location === 'London' && person.age >= 30));
// Given a list of people, give me a new list without the person John
console.log(people.filter(person => person.name !== 'John'));

Applying filters and maps in a pipeline

We can combine filter and map in a pipeline.

Given a list of people, get me a list of the names of the people from London.

var people = [
  { name: 'John', age: 20, location: 'London' },
  { name: 'George', age: 30, location: 'Paris' },
  { name: 'David', age: 40, location: 'London' }
];

console.log(people
  .filter(({location}) => location === 'London')
  .map(({name}) => name));

👉 A keen observer will perhaps realize that the effect of applying a filter and a map to an input list is akin to applying a WHERE and SELECT clause respectively to a table in a relational database. It is also similar to applying a grep followed by a cut or an awk '{print ...} on an input in the Linux command line.

Returning a Function from a Function

Study the following code carefully:

function foo() {
  function bar() {
    console.log('Bar');
  }
  return bar;
}
var x = foo();
x() // Prints 'Bar'

Let us break this down. What happens after the following is executed:

function foo() {
  function bar() {
    console.log('Bar');
  }
  return bar;
}
  1. The function object foo is created.
  2. The variable foo is created.
  3. The variable foo refers to the function object.

Is bar created? No!

Is bar called? No!

Note that bar is defined in the body of foo. Anything that is defined in the body of a function is executed only if the function is called. We haven't called foo yet. So neither is bar created, nor is it called.

What happens when we execute this:

var x = foo;
x();

That was a trick question. Note that when we say x = foo, it means let x refer to the same object as what foo is referring to, which is the first case of first-class functions. So when you call x(), it means, execute the body of foo.

However, the meaning completely changes when we do this:


var x = foo();
x()

This invokes foo and the return value of foo is assigned to x. What does foo return?

Let us analyse the function foo:

function foo() {
  function bar() {
    console.log('Bar');
  }
  return bar;
}

When foo is invoked, the bar variable is created and it refers to the bar function object. This is what is then returned and we assign the return value to x. Thus x is a reference to the return value of foo, which is nothing but the function object bar.

Since bar is defined in the function foo, we say that bar is an inner function of foo.

What is the typeof(x)?

typeof(x) // 'function'

Since x is referring to a function, we can call it. When we call x(), the body of the function that x refers to (which is nothing but bar) is executed. This is what ends up printing "Bar".

We can simplify the inner function by defining and returning the function in one statement:

function foo() {
  return function bar() {
    console.log('Bar');
  }
}

We can make it anonymous as well:

function foo() {
  return function() {
    console.log('Bar');
  }
}
var x = foo();
x();

Finally, we can even make it an arrow function:

function foo() {
  return () => {
    console.log('Bar');
  }
}

If a function call returns a function, then we can call the outer function followed by the inner function in one single statement:

foo()()

Closures

function foo(x) {
  var tmp = 20;
  function bar(y) {
    console.log(x + y + tmp);
  }
  return bar;
}
var p = foo(10);
p(30);

# Set a breakpoint on Line 4
# Run the program in Debug mode
# In Visual Studio Code, it's under Run -> Start Debugging

# The variable y is in the Local scope of bar
# The variables x and tmp are in the Closure scope of foo

Test Your Knowledge

No quiz available

Tags