Returning Functions from Functions in JavaScript

A function that returns another function is a factory for behavior. Learn how closures make this pattern work and why it is the foundation of configuration, partial application, and encapsulation.

6 min read

A function can return another function. When it does, the returned function remembers the variables and arguments from the outer function's scope. This combination -- a returned function plus remembered data -- is one of the most powerful patterns in JavaScript.

Here is the simplest example:

javascriptjavascript
function createGreeter(greeting) {
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}
 
const sayHello = createGreeter("Hello");
const sayHi = createGreeter("Hi");
 
console.log(sayHello("Alex")); // "Hello, Alex!"
console.log(sayHi("Sara"));    // "Hi, Sara!"

The createGreeter function runs once, captures its greeting argument, and returns a new function. That returned function remembers greeting forever, long after createGreeter itself has finished running. This is a closure: the inner function retains access to the outer function's variables even after the outer function has finished, which is what makes each call to createGreeter produce its own independently configured greeter.

How Closures Make This Work

When a function returns another function, JavaScript keeps the outer function's variables alive as long as the inner function exists:

javascriptjavascript
function createCounter() {
  let count = 0; // This variable lives on
 
  return function() {
    count++;
    return count;
  };
}

Calling createCounter twice creates two entirely separate counters, each with its own private count variable that nothing outside the closure can reach directly:

javascriptjavascript
const counterA = createCounter();
const counterB = createCounter();
 
console.log(counterA()); // 1
console.log(counterA()); // 2
console.log(counterB()); // 1 (independent state)

Each call to createCounter creates a new count variable and a new closure. The two counters are completely independent, they do not share state. This is the general shape of a function factory: one function produces any number of specialized functions, each with its own captured configuration, which is cleaner than passing the same argument to every call.

Practical Use: Formatting Functions

Formatting money correctly depends on locale and currency, two values that rarely change once a formatter is set up. A factory captures both once and reuses them on every call:

javascriptjavascript
function createFormatter(locale, currency) {
  return function(amount) {
    return new Intl.NumberFormat(locale, {
      style: "currency",
      currency,
    }).format(amount);
  };
}

Each call to createFormatter produces an independent formatter, so the same factory can serve multiple currencies at once without any of them interfering with each other:

javascriptjavascript
const formatUSD = createFormatter("en-US", "USD");
const formatEUR = createFormatter("de-DE", "EUR");
const formatJPY = createFormatter("ja-JP", "JPY");
 
console.log(formatUSD(19.99)); // "$19.99"
console.log(formatEUR(19.99)); // "19,99 €"
console.log(formatJPY(2000));  // "¥2,000"

Each formatter is configured once and used many times. The locale and currency are baked into the closure.

Practical Use: Logger with Prefix

A similar pattern works for logging. Instead of typing the same level prefix on every call, a factory bakes it into the returned function once:

javascriptjavascript
function createLogger(level) {
  return function(message) {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] [${level}] ${message}`);
  };
}

Each level gets its own logger, and calling one only ever needs the message itself, since the level and the timestamp formatting are already handled inside the closure:

javascriptjavascript
const info = createLogger("INFO");
const warn = createLogger("WARN");
const error = createLogger("ERROR");
 
info("Server started");   // [2026-07-15T...] [INFO] Server started
error("Connection lost"); // [2026-07-15T...] [ERROR] Connection lost

Each of info, warn, and error is a completely separate function, but all three share the same underlying implementation. Only the captured level value differs between them, so fixing a bug in the timestamp formatting or message layout only ever needs to happen in one place.

Practical Use: Rate Limiter

A rate limiter needs to remember how many calls have happened recently, without exposing that counter to outside code that might tamper with it. A closure is a natural fit for that private state. The outer function sets up the counter and a timer that resets it on a fixed interval, then returns a function that checks and updates that counter on every call:

javascriptjavascript
function createRateLimiter(maxCalls, windowMs) {
  let calls = 0;
  setInterval(() => { calls = 0; }, windowMs);
 
  return function() {
    if (calls >= maxCalls) return false;
    calls++;
    return true;
  };
}

Once created, the limiter can be called repeatedly, and it tracks its own count internally, resetting itself on a timer without any outside code having to manage that state:

javascriptjavascript
const limiter = createRateLimiter(3, 1000);
 
console.log(limiter()); // true
console.log(limiter()); // true
console.log(limiter()); // true
console.log(limiter()); // false (limit reached)
// After 1 second, calls resets to 0

The returned function tracks its own call count through the closure. No global variables, no class -- just a function that remembers.

Returning Arrow Functions

The function a factory returns does not have to be a full function expression. When the inner logic is a single expression, an arrow function keeps the whole factory shorter without changing how the closure behaves:

javascriptjavascript
function createValidator(min, max) {
  return (value) => value >= min && value <= max;
}
 
const isValidAge = createValidator(0, 120);
const isValidScore = createValidator(0, 100);
 
console.log(isValidAge(25));   // true
console.log(isValidAge(-5));   // false
console.log(isValidScore(85)); // true
console.log(isValidScore(150)); // false

Both isValidAge and isValidScore are separate closures created from the same factory, each remembering its own min and max bounds. Swapping the returned function for an arrow function did not change the closure mechanics at all, only the syntax used to write it.

Returning Multiple Functions from One Factory

You can return an object containing multiple functions that share the same closure state, instead of returning just one function. Each method below closes over the same private items array:

javascriptjavascript
function createStack() {
  const items = [];
 
  return {
    push(item) { items.push(item); },
    pop() { return items.pop(); },
    peek() { return items[items.length - 1]; },
    size() { return items.length; },
  };
}

Every call to createStack builds a fresh, independent stack. The methods on the returned object are the only way to touch its data:

javascriptjavascript
const stack = createStack();
stack.push("a");
stack.push("b");
console.log(stack.size()); // 2
console.log(stack.peek()); // "b"
console.log(stack.pop());  // "b"
console.log(stack.size()); // 1

The items array is private. Only the returned methods can access it. This is the module pattern -- encapsulation without classes, and it works because every method returned from the same call to createStack shares one closure over the same array.

Every example in this article follows the same underlying shape: an outer function captures some data, then hands back a function or a group of functions that can use that data long after the outer call has finished. Once that shape feels familiar, the specific use case, formatting, logging, rate limiting, validation, or a full data structure, is just a detail.

For more on closures, see JavaScript Closures Deep Dive: Complete Guide. For the broader concept, see Higher Order Functions in JavaScript: Full Guide.

Rune AI

Rune AI

Key Insights

  • A function can return another function, which forms a closure over the outer function's variables.
  • This pattern creates function factories: configure once, use the returned function many times.
  • Each call to the outer function creates a fresh closure with independent state.
  • Use this for configuration, encapsulation, rate limiting, logging, and dependency injection.
  • The returned function remembers the arguments of the outer function, even after the outer function returns.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between a function factory and a class?

Both create objects with behavior. A function factory returns a closure-based function that remembers private state. A class uses this and prototype. Factories are simpler for small, single-purpose objects; classes scale better for many instances with shared methods.

Does returning a function from a function cause memory leaks?

It can if you hold references to closures you no longer need. The returned function keeps the outer function's variables alive. When you are done with a closure, set the reference to null so the garbage collector can free the memory.

Is this the same as currying?

Currying is a specific form of returning functions: a curried function takes one argument at a time, returning a new function for each subsequent argument. Function factories are a broader category that includes currying as one pattern.

Conclusion

Returning a function from a function turns your functions into factories. Each returned function is a closure that remembers the arguments and variables from its creation context. This pattern enables configuration, encapsulation, partial application, and clean stateful behavior without classes or global variables.