JavaScriptIntermediate

Important Function Concepts in JavaScript

Learn anonymous and named functions, first-class functions, IIFEs, and pure and impure functions with practical examples.

All JavaScript lessons

What you will learn

You already know how to create and call functions. Now it is time to understand how JavaScript treats functions as values and how different function styles are used in real programs.

In this lesson, you will learn:

  • Anonymous and named functions
  • Named function expressions
  • First-class functions
  • Passing functions as arguments
  • Returning functions from functions
  • The difference between passing and calling a function
  • IIFE (Immediately Invoked Function Expression)
  • Pure and impure functions
  • Side effects
  • Why pure functions are useful
  • Practical use cases for these concepts

These concepts will become especially useful when you start working with callbacks, higher-order functions, closures, array methods, and asynchronous JavaScript.


Anonymous functions

An anonymous function is a function that does not have its own name.

For example:

const greet = function () {
  console.log("Hello!");
};

greet();

The function itself does not have a name:

function () {
  console.log("Hello!");
}

Instead, the function is stored in the variable greet.

You can also create an anonymous arrow function:

const sayBye = () => {
  console.log("Bye!");
};

sayBye();

Anonymous functions are especially useful when you need a function for a specific place or a one-time operation.

For example:

setTimeout(function () {
  console.log("Runs after 1 second");
}, 1000);

Here, the function is passed directly to setTimeout().

You could give this function a name, but if you only need it there, an anonymous function is often convenient.

Another common example is an event handler:

document.getElementById("btn").addEventListener("click", function () {
  console.log("Button clicked");
});

The function is created specifically for the click event.

These functions are often used as callbacks, which you will study in more detail in a later lesson.

Tip: Use anonymous functions when the function is small and used only in one place. Give important reusable functions meaningful names.


Named functions

A named function has its own name.

For example:

function calculateTotal(price, quantity) {
  return price * quantity;
}

console.log(calculateTotal(100, 3));

Here:

calculateTotal

is the function’s name.

Named functions have several advantages.

Readability

The name can describe what the function does:

function calculateTotal() {
  // ...
}

is easier to understand than an unnamed function whose purpose is not obvious.

Reusability

You can call a named function multiple times:

function calculateTotal(price, quantity) {
  return price * quantity;
}

console.log(calculateTotal(100, 2));
console.log(calculateTotal(250, 4));
console.log(calculateTotal(500, 3));

Easier debugging

When an error occurs, a meaningful function name can make the error trace easier to understand.

For example:

function calculateTotal() {
  // some logic
}

is easier to identify in debugging information than an unnamed function.


Named function expressions

A function expression can also have a name.

For example:

const factorial = function calculateFactorial(n) {
  if (n <= 1) {
    return 1;
  }

  return n * calculateFactorial(n - 1);
};

console.log(factorial(5));

Output:

120

Here:

function calculateFactorial(n)

is a named function expression.

The name calculateFactorial is available inside the function itself.

This can be useful when a function needs to call itself, which is known as recursion.

You will study recursion separately in more detail.

Good habit: Give important functions meaningful names. Use anonymous functions when a small function is only needed at the place where it is used.


First-class functions

This is one of the most important ideas in JavaScript.

In JavaScript:

Functions are values.

That means you can treat a function like other values such as numbers, strings and objects.

Because JavaScript allows functions to be treated as values, functions are called first-class functions.

Let’s see what that means.


Store a function in a variable

You can store a function in a variable:

const sayHello = function () {
  return "Hello!";
};

console.log(sayHello());

You can also assign that function to another variable:

const sayHello = function () {
  return "Hello!";
};

const greetAgain = sayHello;

console.log(greetAgain());

Output:

Hello!

Both variables refer to the same function.

You can also check its type:

console.log(typeof sayHello);

Output:

function

This demonstrates that a function can be treated as a value.


Store functions in an object

Functions can also be stored as object properties.

const calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b
};

console.log(calculator.add(10, 5));
console.log(calculator.subtract(10, 5));
console.log(calculator.multiply(10, 5));

Output:

15
5
50

You can also access a function property using bracket notation:

console.log(calculator["multiply"](10, 5));

Output:

50

This is useful when different operations need to be selected dynamically.


Pass a function as an argument

Because functions are values, you can pass a function to another function.

For example:

function calculate(operation, x, y) {
  return operation(x, y);
}

const add = (a, b) => a + b;

const power = (a, b) => a ** b;

console.log(calculate(add, 10, 5));
console.log(calculate(power, 2, 5));

Output:

15
32

Look carefully at:

calculate(add, 10, 5);

We are passing add as a value.

We are not writing:

calculate(add(), 10, 5);

The calculate() function receives another function and decides when to use it.

A function that accepts another function as an argument is called a higher-order function.

You will study higher-order functions in more detail in a later lesson.


Return a function from a function

A function can also return another function.

For example:

function makeGreeter(greeting) {
  return function (name) {
    return `${greeting}, ${name}!`;
  };
}

Now we can create customized functions:

const sayHi = makeGreeter("Hi");
const sayNamaste = makeGreeter("Namaste");

console.log(sayHi("Riya"));
console.log(sayNamaste("Karan"));

Output:

Hi, Riya!
Namaste, Karan!

The makeGreeter() function creates and returns another function.

The returned function remembers the greeting value.

This behavior is related to closures, which you will study separately.


Passing a function vs calling a function

This is a very common beginner mistake.

Consider:

function sayHello() {
  console.log("Hello!");
}

If you write:

setTimeout(sayHello, 1000);

you are passing the function.

You are telling JavaScript:

“Use this function later.”

But:

setTimeout(sayHello(), 1000);

is different.

Here:

sayHello()

means:

“Call the function right now.”

So remember:

sayHello

means:

The function itself.

Whereas:

sayHello()

means:

Execute the function now.

This distinction becomes extremely important when working with callbacks and event handlers.


Using functions as a lookup table

Because functions can be stored as values, you can use an object to select an operation.

For example:

const operations = {
  "+": (a, b) => a + b,
  "-": (a, b) => a - b,
  "*": (a, b) => a * b,
  "/": (a, b) => a / b
};

const operator = "*";

if (operator in operations) {
  console.log(operations[operator](10, 5));
} else {
  console.log("Unknown operator");
}

Output:

50

Instead of writing a long switch statement, we can store operations as functions and select the required function dynamically.

This pattern is often called a lookup table.


IIFE: Immediately Invoked Function Expression

An IIFE stands for:

Immediately Invoked Function Expression

An IIFE is a function that is created and executed immediately.

Example:

(function () {
  console.log("I run immediately!");
})();

Output:

I run immediately!

The function is created and then immediately called.

You can also write an IIFE using an arrow function:

(() => {
  console.log("Me too!");
})();

How does an IIFE work?

Consider:

(function () {
  console.log("Hello");
})();

There are two important parts.

Part 1: Create the function expression

(function () {
  console.log("Hello");
})

The parentheses turn the function into an expression.

Part 2: Call the function

();

The final parentheses immediately call the function.

Together:

(function () {
  console.log("Hello");
})();

The function is created and immediately executed.


Why use an IIFE?

One historical use of IIFEs was creating a private scope.

For example:

(function () {
  const secret = "Only visible here";

  console.log(secret);
})();

The variable secret exists inside the IIFE.

Outside the IIFE:

console.log(typeof secret);

Output:

undefined

The variable does not exist in the surrounding scope.

Before modern JavaScript features such as let, const, and modules became common, IIFEs were widely used to avoid polluting the global scope.

You may still encounter IIFEs in older JavaScript code and libraries.

Good to know: Modern JavaScript has modules using import and export, so IIFEs are less necessary for this purpose today. However, understanding them is still important because you will encounter them in existing code.


IIFE with a return value

An IIFE can also return a value.

const total = (function (a, b) {
  return a + b;
})(10, 20);

console.log(total);

Output:

30

The function is immediately executed with:

(10, 20)

and its return value is stored in total.


IIFE for private data

An IIFE can be used to create data that cannot be accessed directly from outside.

For example:

const counter = (function () {
  let count = 0;

  return {
    increment() {
      count++;
      return count;
    },

    getCount() {
      return count;
    }
  };
})();

Now:

console.log(counter.increment());
console.log(counter.increment());
console.log(counter.getCount());

Output:

1
2
2

But:

console.log(counter.count);

returns:

undefined

The count variable is private.

This pattern is related to closures and was commonly used to create private data before JavaScript had more modern module and class features.


Pure and impure functions

Now let’s look at another important concept:

Pure vs impure functions.

This concept helps you understand how predictable and safe a function is.


Pure functions

A function is considered pure when it follows two main rules:

  1. The same input always produces the same output.
  2. It does not produce side effects.

For example:

function add(a, b) {
  return a + b;
}

console.log(add(2, 3));
console.log(add(2, 3));

Both calls always produce:

5

The function does not modify anything outside itself.

Another example:

const calculateTotal = (price, quantity, taxRate) => {
  return price * quantity * (1 + taxRate);
};

And:

const formatName = (first, last) => {
  return `${first} ${last}`.trim();
};

These functions receive values and return results without changing external data.

A useful way to think about a pure function is as a calculator.

You give it the same inputs, and it gives you the same answer.


Impure functions

A function is impure when it breaks one or both rules of a pure function.

An impure function may:

  • Depend on external data
  • Modify external data
  • Change its arguments
  • Use randomness
  • Depend on the current time
  • Print something
  • Modify the webpage
  • Write to storage
  • Communicate with a server

Let’s look at some examples.


Impure function: depends on an outside variable

let taxRate = 0.18;

function addTax(price) {
  return price + price * taxRate;
}

Now:

console.log(addTax(100));

Output:

118

Change the external variable:

taxRate = 0.28;

console.log(addTax(100));

Output:

128

The input is still:

100

but the output changed because the function depends on an outside variable.

Therefore, the function is impure.


Impure function: changes something outside

Consider:

let total = 0;

function addToTotal(amount) {
  total += amount;
}

Now:

addToTotal(50);
addToTotal(50);

console.log(total);

Output:

100

The function changes the external variable total.

That change is a side effect.


Impure function: randomness

Consider:

const rollDice = () => {
  return Math.floor(Math.random() * 6) + 1;
};

Calling it multiple times can produce different results:

console.log(rollDice());
console.log(rollDice());

The same input produces no guaranteed output because the function depends on randomness.

Therefore, it is impure.


Impure function: current time

Consider:

const now = () => Date.now();

Calling the function at different times can produce different results.

console.log(now());
console.log(now());

The result depends on the current time.

Therefore, the function is impure.


Side effects

A side effect happens when a function changes or interacts with something outside its own calculation.

For example:

function showMessage(message) {
  console.log(message);
}

The function prints something to the Console.

Printing is a side effect because the function is interacting with something outside its return value.

Other common side effects include:

Changing the DOM
Writing to localStorage
Changing an external variable
Making an API request
Writing to a file
Updating a database
Printing to the Console

This does not mean side effects are bad.

Real applications need side effects.

Without them, your application could not display information, save data, or communicate with a server.

The goal is to keep side effects controlled and easy to identify.


Impure function: changing an argument

This is another common source of unexpected behavior.

Consider an array:

const cart = ["shirt"];

Now:

function addItemImpure(list, item) {
  list.push(item);

  return list;
}

Call it:

addItemImpure(cart, "shoes");

console.log(cart);

Output:

["shirt", "shoes"]

The original cart array has been changed.

The function modified the object it received.


A pure alternative

Instead of modifying the original array, we can create a new array:

function addItemPure(list, item) {
  return [...list, item];
}

Now:

const original = ["shirt"];

const updated = addItemPure(original, "shoes");

console.log(original);
console.log(updated);

Output:

["shirt"]
["shirt", "shoes"]

The original array remains unchanged.

The function creates and returns a new array.


Quick test: Pure or impure?

Look at these functions:

Function Pure? Reason
(a, b) => a + b Yes Same input gives same output and no side effects
(text) => text.toUpperCase() Yes Returns a new string without changing outside data
() => Math.random() No Result can change each time
() => new Date() No Depends on current time
(msg) => console.log(msg) No Produces a side effect
(arr) => arr.push(1) No Changes the original array
(arr) => [...arr, 1] Yes Creates a new array

Why do pure functions matter?

Pure functions have several useful properties.

Predictable

You can understand their behavior by looking at their inputs.

Easy to test

You can provide input and compare the result.

add(2, 3);

should always return:

5

Easy to debug

There are fewer hidden changes to investigate.

Easy to reuse

A pure function can generally be used in many places without worrying about unexpected external changes.


Impure does not mean bad

It is important to understand this:

Impure functions are not automatically bad functions.

Real applications need side effects.

For example:

console.log("Hello");

is a side effect.

Changing the webpage is a side effect.

Making an API request is a side effect.

Saving data is a side effect.

All of these are necessary in real applications.

A useful programming approach is:

Keep your core logic as pure as possible and keep side effects in small, clearly identified functions.

For example:

const calculateTotal = (prices) => {
  let total = 0;

  for (const price of prices) {
    total += price;
  }

  return total;
};

This function only calculates.

Then:

const showTotal = (prices) => {
  console.log("Total: ₹" + calculateTotal(prices));
};

This function handles the side effect of displaying the result.

Now the calculation and output are separated.


Comparing function styles

You have already seen function declarations, function expressions and arrow functions.

Here is a quick comparison:

Function Declaration Function Expression Arrow Function
Example function add() {} const add = function () {} const add = () => {}
Has a name Yes Optional Usually uses variable name
Can be called before definition Yes No No
Own this Yes Yes No
Common use Main named functions Callbacks and flexible function values Short callbacks and helpers

The this behavior shown above will be covered separately when you study this and related JavaScript concepts.

For now, focus on understanding how functions can be created and used as values.


Real-life use cases

1. Event handler with an anonymous function

const button = document.getElementById("btn");

button.addEventListener("click", () => {
  console.log("Saved!");
});

The arrow function is used directly as the event callback.


2. Run setup code once with an IIFE

(function init() {
  const startTime = "9:00";

  console.log("App started at " + startTime);
})();

The function runs immediately.


3. Create customized functions

const createTaxCalculator = (rate) => {
  return (amount) => amount * rate;
};

const gst18 = createTaxCalculator(0.18);
const gst5 = createTaxCalculator(0.05);

console.log(gst18(1000));
console.log(gst5(1000));

Output:

180
50

The outer function creates customized functions.


4. Select a function based on a choice

const shippingCost = {
  standard: (weight) => weight * 10,
  express: (weight) => weight * 25 + 50,
  free: () => 0
};

const method = "express";

console.log(shippingCost[method](2));

Output:

100

The selected function is called dynamically.


5. Separate calculation from display

const applyDiscount = (price, percent) => {
  return price - (price * percent) / 100;
};

const addGST = (price) => {
  return price * 1.18;
};

const formatPrice = (price) => {
  return "₹" + price.toFixed(2);
};

const finalPrice = formatPrice(
  addGST(
    applyDiscount(1000, 10)
  )
);

console.log(finalPrice);

Output:

₹1062.00

Each function performs one small task.

This style makes functions easier to test, reuse and combine.


6. Passing a callback correctly

function showAlert() {
  console.log("Time is up!");
}

setTimeout(showAlert, 500);

The function is passed to setTimeout().

Do not write:

setTimeout(showAlert(), 500);

because that calls showAlert() immediately.


Common mistakes

  • Calling instead of passing: setTimeout(greet(), 1000) runs greet immediately. Use setTimeout(greet, 1000) when you want to pass the function.
  • Forgetting IIFE parentheses: function () {}() is not a valid function declaration. Use (function () {})().
  • Forgetting the semicolon before an IIFE: In some situations, JavaScript can join the previous statement with the IIFE. Ending the previous statement with ; avoids this problem.
  • Expecting function expressions to behave like declarations: Function declarations are hoisted differently from function expressions.
  • Changing an argument and assuming the function is pure: Methods such as push() and sort() can modify the original array.
  • Hiding dependencies in outside variables: Passing required values as parameters generally makes a function easier to understand and test.
  • Thinking impure functions are always bad: Side effects are necessary in real applications. The goal is to keep them controlled.
  • Making every function anonymous: Important reusable functions should generally have meaningful names.
  • Mixing calculation and display: Keep core calculations separate from UI or output-related side effects when practical.

Practice

Practice 1 — Anonymous function

Create an anonymous function that prints:

Hello

Store it in a variable and then call the function.

After that, create the same functionality using an arrow function.


Practice 2 — Functions inside an object

Create an object named mathTools with three arrow functions:

  • square
  • cube
  • half

For example:

const mathTools = {
  square: (n) => n * n,
  cube: (n) => n * n * n,
  half: (n) => n / 2
};

Call all three functions with different values.


Practice 3 — Pass a function as an argument

Create:

function applyTwice(fn, value) {
  return fn(fn(value));
}

Test it with:

(n) => n + 3

and:

(text) => text + "!"

Practice 4 — Return a function

Create a function:

makeMultiplier(factor)

It should return another function.

Then create:

double

and:

triple

using makeMultiplier().

Test both functions.


Practice 5 — Create an IIFE

Write an IIFE that:

  1. Creates a variable named message
  2. Prints the message
  3. Checks typeof message outside the IIFE

Verify that the variable is not available outside the IIFE.


Practice 6 — Private counter

Create a private counter using an IIFE.

The returned object should have:

  • increment()
  • decrement()
  • getCount()

The actual counter value should remain private.


Practice 7 — Pure or impure?

Decide whether each function is pure or impure and explain why.

const a = (x) => x * 2;

const b = (list) => list.sort();

const c = () => Math.random();

const d = (text) => text.trim().toLowerCase();

let n = 0;

const e = () => ++n;

Practice 8 — Convert an impure function to a pure function

Consider:

const user = {
  name: "Riya",
  points: 10
};

function addPoints(player, extra) {
  player.points += extra;
  return player;
}

Rewrite the function so that it does not modify the original object.

The function should return a new object with the updated points.

For example:

{
  name: player.name,
  points: player.points + extra
}

Practice 9 — Replace a switch with functions

Replace this logic with a lookup-table object containing functions:

switch (action) {
  case "greet":
    return "Hello";

  case "bye":
    return "Goodbye";

  default:
    return "Unknown";
}

Create an object where each action points to a function.


Practice 10 — Challenge: Function composition

Create a function:

compose(f, g)

It should return a new function where:

compose(f, g)(x)

produces the same result as:

f(g(x))

Test your solution using small helper functions such as trimming text and converting it to uppercase.


Recap

  • Anonymous functions do not have their own name and are commonly useful for one-off callbacks.
  • Named functions are easier to read, reuse and debug and can be useful for recursion.
  • JavaScript has first-class functions, which means functions can be treated as values.
  • Functions can be stored in variables and objects.
  • Functions can be passed as arguments to other functions.
  • Functions can return other functions.
  • sayHello refers to the function itself, while sayHello() calls the function immediately.
  • An IIFE is an Immediately Invoked Function Expression that runs as soon as it is created.
  • IIFEs can create a private scope and can be used for setup code and private data.
  • A pure function produces the same output for the same input and does not create side effects.
  • An impure function may depend on or modify things outside itself.
  • Side effects include things such as printing, changing the DOM, modifying external variables, saving data and making network requests.
  • Impure functions are not automatically bad; real applications need side effects.
  • Keeping core logic pure and separating side effects can make code easier to test, debug and reuse.

Next: You will learn about Scope and Hoisting — where variables live in JavaScript and how JavaScript handles declarations before your code executes.