skip to content

Functions as Values

Functions in JavaScript are values you can store, pass, return, and pre-configure — the foundation of every callback API and every abstraction you will write. This group covers what you can do with a function once you stop thinking of it as just a named block of code.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

17

In JavaScript, what makes a function a higher-order function, and which property of the language makes higher-order functions possible?

level: juniorimportance: must knowfreq 82%

answer

  1. functions are values, not syntax
  2. two shapes: takes one, returns one
  3. callback is a role, not a type
  4. name versus name-with-parentheses

basics

~20 s

A higher-order function takes a function as an argument, returns a function, or both. It works because JavaScript functions are first-class values: they can be stored in variables, put in arrays or objects, passed as arguments, and returned like any other value.

solid answer

~40 s

A function is higher-order if it operates on functions — it accepts one or more functions as arguments, returns a function, or does both. Nothing special enables this: in JavaScript a function *is* a value, a callable object, so `const f = function () {}` stores it, `arr.push(f)` puts it in an array, and `run(f)` hands it to someone else exactly like a number or a string. Array methods such as `map`, `filter` and `sort` are higher-order because they take a callback; a factory like `makeAdder(5)` is higher-order because it returns a function. The single mistake to avoid is writing `run(f())` when you meant `run(f)` — the parentheses call the function and pass its *result* instead of the function itself.

code

javascript · 14 lines
javascript
// Shape 1: takes a function
function applyTwice(fn, value) {
  return fn(fn(value));
}

// Shape 2: returns a function
function makeAdder(step) {
  return (n) => n + step;
}

const addTen = makeAdder(10);
console.log(applyTwice(addTen, 5)); // 25
console.log(typeof addTen);         // "function"
console.log(addTen.name);           // "" (anonymous arrow returned from makeAdder)

go deeper

for a junior

Be ready to state the definition and write both shapes on a whiteboard: one function that takes another, one that returns another. Say plainly that functions are values in JavaScript.

for a middle

Explain the argument contract the higher-order function imposes — how many arguments the callback receives and what its return value is used for — and show the passing-versus-calling bug and how you spot it in review.

for a senior

Show judgment about when the abstraction pays for itself. An interviewer expects you to point out that a callback inverts control: the caller supplies behaviour but the callee owns timing, error handling, and how many times the callback runs.

for a principal

Own the API-design angle: whether an extension point should be a function, a configuration object, or data, and what handing users a callback commits your library to — invocation timing, argument stability, and error propagation you can no longer change without a breaking release.

## Functions are values In JavaScript a function is an ordinary value — specifically, an object that happens to be callable. Anything you can do with a number you can do with a function: bind it to a variable, store it in an array, use it as an object property, pass it to another function, or return it from one. This is what "first-class functions" means, and it is the entire foundation of the higher-order style. ```js const double = (n) => n * 2; const ops = [double, (n) => n + 1]; // stored in an array const registry = { double }; // stored as a property console.log(typeof double); // "function" ``` Note that `typeof` reports `"function"`, but a function is still an object underneath: it has properties (`double.name` is `"double"`), and you can attach your own. ## The two shapes A higher-order function is any function that **takes a function as an argument**, **returns a function**, or both. Taking a function — the callback shape: ```js function applyTwice(fn, value) { return fn(fn(value)); } applyTwice((n) => n + 3, 1); // 7 ``` `applyTwice` knows nothing about what `fn` does. The caller supplies the behaviour; `applyTwice` supplies the control flow. That inversion is the whole point. Returning a function — the factory shape: ```js function makeAdder(step) { return (n) => n + step; } const addTen = makeAdder(10); addTen(5); // 15 ``` `makeAdder` is called once and produces a specialised function you can call many times. Standard-library examples of the first shape are everywhere: `Array.prototype.map`, `Array.prototype.filter`, `Array.prototype.sort` and `Array.prototype.forEach` all accept a callback; `Object.defineProperty` accepts getter and setter functions. ## Callback is a role, not a type The word *callback* describes a function's role rather than anything about the function itself: it is simply a function that somebody else will call, at a time of their choosing. Any function can be one. The receiving code decides how many arguments to pass it, when to call it, and what to do with its return value — that contract is defined entirely by the higher-order function, and it is why reading the documentation of the callback signature matters. ## Passing versus calling The most common beginner bug is a stray pair of parentheses: ```js function schedule(task) { /* stores task, runs it later */ } schedule(cleanup); // passes the function — usually what you want schedule(cleanup()); // CALLS cleanup now and passes its return value ``` Both lines are valid JavaScript, so nothing throws. The second one runs `cleanup` immediately and then hands `schedule` whatever it returned — very often `undefined`, which then fails much later with a confusing "task is not a function" message. Whenever you write a function name into an argument list, check whether you meant the name or the call. ## Why the pattern is worth learning Higher-order functions let you factor out *what varies*. Instead of writing one loop per operation, you write one traversal that accepts the operation. Instead of ten near-identical validators, you write one validator factory parameterised by a rule. Event-driven and asynchronous APIs are unavoidably higher-order — you cannot hand the runtime code to run later except by handing it a function. Composition helpers, wrappers such as `once` or `memoize`, and middleware chains are all just functions that take and return functions. A small warning about vocabulary: not every function-taking function is worth abstracting. Interviewers ask you to write a higher-order function from scratch mainly to check that you really see a function as a value, not as a piece of syntax that must be immediately invoked. Demonstrating that by writing a four-line factory is worth more than reciting a definition.

  • Is every function that accepts a callback worth calling higher-order, or is the term reserved for library code?
    The term is purely structural — any function whose parameter or return value is a function qualifies, whether it is a three-line helper in your own file or `Array.prototype.map`. There is no threshold of sophistication. What varies is usefulness: the abstraction earns its keep when it removes real duplication, not merely because it accepts a function.
  • What actually happens if I write `schedule(cleanup())` instead of `schedule(cleanup)`?
    `cleanup` runs immediately, at the moment the argument list is evaluated, and its return value is passed to `schedule`. If `cleanup` returns nothing, `schedule` receives `undefined`, stores it, and blows up later with something like "task is not a function" — far from the real mistake. Nothing throws at the call site, which is what makes it a slow bug.
  • How can a function be both an object and callable at the same time?
    Because a JavaScript function is an object with an internal call behaviour. `typeof` reports `"function"` to flag that it is callable, but it still has properties: `name`, `length`, and any property you attach yourself. That is why you can write `handler.cancelled = true` on a function you passed somewhere, and why functions can live in arrays and Maps like any other object.

A recipe card is not the meal. Passing the card lets someone cook whenever they like; cooking it yourself and handing over the plate is a different act — that is the difference between passing fn and passing fn().

saying these in an interview costs you the question

  • Claims JavaScript needs special syntax to pass a function
  • Writes fn() in an argument list when passing fn was meant
  • Thinks only built-in array methods can be higher-order
  • Says a callback must be anonymous or an arrow function
  • Confuses returning a function with returning its call result

context

open as a page

In JavaScript, when does a default parameter value actually apply, and what happens if the caller passes null?

level: juniorimportance: must knowfreq 75%

basics

~20 s

A default applies only when the argument is undefined — omitted, or explicitly passed as undefined. Every other value, including null, 0, empty string, false and NaN, is used as-is, so null never triggers the default.

open as a page

What does `['1', '7', '11'].map(parseInt)` return, and what does that result reveal about how a higher-order function calls the callback you hand it?

level: middleimportance: must knowfreq 60%

basics

~20 s

It returns [1, NaN, 3]. The callback is invoked with three arguments — element, index and array — so parseInt receives the index as its radix: radix 1 is invalid and radix 2 reads "11" as binary 3. Wrap the callback to control its arity.

open as a page

In JavaScript, what does a ...rest parameter give you that the legacy arguments object does not?

level: middleimportance: must knowfreq 70%

basics

~20 s

A rest parameter binds a real Array, so map, filter and reduce work directly, and it holds only the arguments beyond the named ones. The arguments object is array-like, holds every argument, is not available in arrow functions, and needs conversion before array methods work.

open as a page

How does `Function.prototype.bind` let you partially apply a JavaScript function, and what happens to arguments passed later to the bound function?

level: juniorimportance: should knowfreq 42%

basics

~20 s

Every argument after bind's first is stored and prepended to each later call, so greet.bind(null, 'Hi') returns a function that always supplies 'Hi' first and appends whatever the caller passes. Only leading arguments can be pinned this way.

open as a page

In JavaScript, what is the difference between currying a function and partially applying it?

level: middleimportance: should knowfreq 55%

basics

~20 s

Currying turns an n-argument function into a chain of n calls that each take one argument. Partial application fixes some arguments once and hands back a function that takes the remaining ones — a single step, not a chain.

open as a page

Write a `curry(fn)` helper in JavaScript so that both `curry(fn)(a)(b)(c)` and `curry(fn)(a, b)(c)` produce the same result. How does it decide when to finally call `fn`?

level: middleimportance: should knowfreq 48%

basics

~20 s

A curry helper returns a collector function that accumulates arguments across calls and invokes the original only once the collected count reaches the function's declared arity, fn.length; until then it returns a new collector holding the arguments gathered so far.

open as a page

In JavaScript, write `compose` and `pipe` helpers that combine several single-argument functions into one. How do they differ, and what constrains the functions you can put in them?

level: middleimportance: should knowfreq 44%

basics

~20 s

Both fold a list of functions into one. pipe applies left to right, compose right to left, matching mathematical notation. Each stage receives exactly one value — the previous stage's return value — so every function after the first must take one argument and return the input the next one expects.

open as a page

Implement `once(fn)`: a higher-order function returning a wrapper that runs `fn` at most one time and returns that first result on every later call. What must the wrapper get right?

level: middleimportance: should knowfreq 45%

basics

~20 s

Return a wrapper holding a called flag and a cached result: on the first call it forwards every argument to fn, stores the return value and flips the flag; afterwards it skips fn and returns the cached value. Forwarding arguments and returning the value are the parts people drop.

open as a page

In JavaScript, when are default parameter expressions evaluated, and can one default refer to another parameter?

level: middleimportance: should knowfreq 42%

basics

~20 s

Default expressions are evaluated at call time, lazily, left to right, and only for parameters that are undefined. A default may reference parameters to its left; referencing one to its right throws a ReferenceError because that binding is still in its temporal dead zone.

open as a page

You are asked to write a general-purpose `memoize(fn)` wrapper for a production codebase. How do you build the cache key, and what makes memoization unsafe for a given function?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Memoize only pure, deterministic functions. Key a single primitive argument directly in a Map; for several arguments or object arguments you need an explicit key function, because stringifying arguments both collides and misses. Bound the cache — an unbounded one grows for the life of the process.

open as a page

In JavaScript, why is `function connect({ retries = 3, timeout = 1000 } = {})` written with the trailing `= {}`, and what call still breaks it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The trailing = {} lets the function be called with no argument at all: without it, destructuring undefined throws a TypeError. It does not protect against an explicit null, which is not undefined, so the default never applies and destructuring null still throws.

open as a page

In JavaScript, what does spreading an array into a call such as `Math.max(...numbers)` actually do, and why can it fail when the array is very large?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Spread at a call site iterates the value and passes each element as a separate argument. With a very large array that means hundreds of thousands of individual arguments, which exceeds the engine's argument limit and throws a RangeError, so large inputs need chunking or a reduce loop instead.

open as a page

You are designing a library function that accepts a user-supplied callback. What contract must you fix and document before shipping it, and why is inconsistent invocation timing the dangerous one?

level: principalimportance: should knowfreq 26%

basics

~20 s

Fix four things: which arguments the callback receives, how many times and in what order it may be called, whether it is invoked synchronously or always deferred, and what happens if it throws. Timing is the dangerous one because a function that sometimes calls back synchronously makes caller state nondeterministic.

open as a page

What does a JavaScript function's `length` property report, and how do default and rest parameters change it?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

A function's length is its declared arity: the number of parameters before the first one with a default value and before any rest parameter. Defaults and rest contribute nothing, and a destructuring pattern counts as one parameter.

open as a page

A shared utility module curries helpers with a `curry(fn)` that completes once collected arguments reach `fn.length`. After a colleague adds a default value to one helper's last parameter, curried call sites start producing wrong numbers and `NaN`. What happened, and how would you fix it?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Adding a default value shrinks fn.length, because it counts only parameters declared before the first default or rest parameter. The curry helper therefore believes it has enough arguments early, calls through too soon, and the still-outstanding argument corrupts the result downstream.

open as a page

When is a curried, point-free style worth adopting across a JavaScript codebase, and what concrete costs would you weigh against it?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Curried, point-free helpers pay off where the same transformations are specialised repeatedly over uniform data with a stable data-last argument order. The costs are debuggability — anonymous closures in stack traces and values that silently stay functions — plus a real onboarding tax.

open as a page