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.
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:
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:
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:
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:
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:
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:
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:
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 lostEach 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:
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:
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 0The 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:
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)); // falseBoth 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:
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:
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()); // 1The 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
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.
Frequently Asked Questions
What is the difference between a function factory and a class?
Does returning a function from a function cause memory leaks?
Is this the same as currying?
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.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.