AlgoInsight

Free lesson · JavaScript Basic to Advance

First-Class Functions

Easy5 min read

Functions Are Values

In JavaScript, functions are first-class citizens. That phrase has a precise meaning: functions can be treated exactly like any other value. They can be stored in variables, passed as arguments, returned from other functions, stored in arrays and objects, and compared. Anything you can do with a number or a string, you can do with a function.

// Store a function in a variable
const greet = function(name) {
  return `Hello, ${name}`;
};

// Store a function in an array
const operations = [
  n => n + 1,
  n => n * 2,
  n => n - 3
];

// Store functions as object properties
const calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};

// Pass a function as an argument
[1, 2, 3].map(greet);

// Return a function from another function
function makeGreeter(salutation) {
  return name => `${salutation}, ${name}`;
}

console.log(greet("Olena"));
console.log(operations[0](5));
console.log(calculator.add(2, 3));
console.log(makeGreeter("Hello")("World"));

Every line above treats a function as a value. None of this is special. None of it requires any unusual setup. It's the default state of JavaScript.

If you came to JavaScript from a language where functions aren't first-class — older versions of Java, C, certain scripting languages — this is the conceptual shift that takes the longest to internalize. Once you have it, the rest of JavaScript becomes much easier.

What "First-Class" Actually Means

Programming language theorists use "first-class" to describe entities that can be used the same ways as any other value in the language. To be first-class, something must be able to be:

  1. Assigned to a variable
  2. Passed as an argument to functions
  3. Returned from functions
  4. Stored in data structures (arrays, objects, sets, maps)
  5. Compared for equality
// 1. Assigned to a variable
const fn = function() { return 42; };

// 2. Passed as an argument
function callIt(f) { return f(); }
console.log(callIt(fn));

// 3. Returned from a function
function returnIt() { return fn; }
console.log(returnIt()());

// 4. Stored in data structures
const arr = [fn, fn, fn];
const obj = { method: fn };

// 5. Compared for equality
console.log(fn === fn);                          // true
console.log(fn === function() { return 42; });   // false (different function values)

Each of those works exactly like it would with a number or a string. Functions are values. The fact that they happen to be callable is one of their many properties, not a special status.

Interactive animation: A function is an object with properties - being callable is just one of them.

Why This Matters

The first-class nature of functions is what enables almost everything interesting in modern JavaScript:

Higher-order functions (Topic 5.6) only work because functions can be passed as arguments. map, filter, reduce, every callback-based API — all of these depend on functions being values.

Closures (Module 6) work because functions can be returned from functions. The returned function captures its enclosing scope, which is what makes closure behavior possible.

Event handling works because functions can be stored and called later. button.addEventListener("click", handler) stores handler and calls it when a click happens.

Promises and async (Module 9) work because callbacks can be stored and called asynchronously. Every .then() takes a function that gets called when the promise resolves.

Functional composition — combining small functions into larger ones — is only possible because functions can be passed and returned. This is the basis of functional programming styles in JavaScript.

graph TD
    A[Functions Are Values] --> B[Higher-Order Functions]
    A --> C[Closures]
    A --> D[Event Handlers]
    A --> E[Promises and Async]
    A --> F[Function Composition]
    A --> G[Currying and Partial Application]

    style A fill:#7c3aed,color:#fff
This diagram shows the consequences of functions being first-class values in JavaScript. Because functions can be passed around like any other value, JavaScript supports higher-order functions, closures, event handlers, promises and async code, function composition, and currying. All of these patterns depend on the same foundational property: functions are values.

Storing Functions in Data Structures

Storing functions in arrays and objects is more common than you might expect. It's how you build dispatch tables, command patterns, and configurable systems.

// A dispatch table: choose a function by key
const operations = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b
};

function calculate(operation, a, b) {
  return operations[operation](a, b);
}

console.log(calculate("add", 10, 5));
console.log(calculate("multiply", 10, 5));
console.log(calculate("divide", 10, 5));

The operations object is a map from string keys to function values. Looking up an operation by name returns the function itself, which we then call. This pattern replaces long if/else or switch chains and makes the system easy to extend — adding a new operation just means adding a new property.

Storing functions in arrays:

const middleware = [
  data => ({ ...data, validated: true }),
  data => ({ ...data, sanitized: true }),
  data => ({ ...data, logged: true })
];

function processData(initialData, middlewareList) {
  return middlewareList.reduce((data, fn) => fn(data), initialData);
}

const result = processData({ value: "hello" }, middleware);
console.log(result);

An array of functions, each transforming data, applied in sequence with reduce. This is the basic shape of middleware in Express.js, Redux, and countless other frameworks. It works because functions are values that can be stored in arrays and iterated over.

Interactive animation: compose: functions building functions - a list of small functions applied in sequence as one pipeline.

Function Identity

Because functions are values, two function expressions that look identical are still different values — they don't equal each other:

const a = function() { return 1; };
const b = function() { return 1; };
const c = a;

console.log(a === a);   // true
console.log(a === b);   // false — different function values
console.log(a === c);   // true — same function, different variable name

a and b are two separate function objects that happen to have identical behavior. a and c are the same function object, accessed through two different variable names.

This matters for addEventListener and removeEventListener. To remove a listener, you have to pass the same function value you passed when adding it:

// This works
const handler = () => console.log("clicked");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);   // same function — removes correctly

// This does NOT work
button.addEventListener("click", () => console.log("clicked"));
button.removeEventListener("click", () => console.log("clicked"));   // different functions — does NOT remove

The two arrow functions in the second example look identical, but they're separate function values. removeEventListener looks for the exact same function value that was added — and it doesn't find a match. This is a common bug.

Common Pitfalls

Interactive animation: Pass it, do not run it - wrapping the call in an arrow hands setTimeout a function to call later.

Warning: The first-class function traps.

Calling instead of referencing. setTimeout(handler, 1000) passes the function. setTimeout(handler(), 1000) calls it now and passes whatever it returned. Beginners constantly mix these up.

Comparing functions by appearance. Two functions with identical code are still different values. To compare functions meaningfully, you have to compare references, not behavior.

Forgetting function identity matters. Anonymous functions used as event listeners can't be removed later because there's no reference to them. Always assign event handlers to variables if you might need to remove them.

Why This Matters in Interviews

Interview: First-class function questions appear in interviews as conceptual probes: "What does it mean that functions are first-class in JavaScript?" The expected answer covers the five properties from this Topic — variable assignment, argument passing, return values, data structure storage, and equality comparison — and ideally connects them to practical patterns like callbacks and closures.

Practical questions also probe whether you understand function identity. "Why doesn't this removeEventListener work?" is a real bug pattern, and senior candidates are expected to spot it.

Topic 5.8 — Interview Lens: Functions and Higher-Order Patterns — covers the deeper interview treatment.

Try It

Exercise: Build a simple command pattern using functions stored in an object. The system should support:

  1. A commands object that maps string names to functions
  2. A register(name, fn) function that adds a command
  3. A run(name, ...args) function that calls the command with the given arguments
  4. An unregister(name) function that removes a command

Then register three commands (your choice), run them, and unregister one.

const commands = {};

function register(name, fn) {
  // your code
}

function run(name, ...args) {
  // your code
}

function unregister(name) {
  // your code
}

This is a real pattern used in CLI tools, plugin systems, and event-driven architectures. Building it from scratch makes the "functions are values stored in objects" mental model concrete.

Back to JavaScript Basic to Advance