Factory function
A factory function is a function that returns other functions. When called, it creates and hands back a new function whose behaviour depends on the arguments you passed in, which is exactly why it is called a factory.
Theory
TL;DR
- A factory is a function whose result is another function.
- It is built on closures: the produced function remembers the factory's arguments.
- Every call to the factory yields an independent instance with its own state.
- Main benefits: reuse of logic, data encapsulation, flexibility.
- Typical examples: counters, filters, loggers, partial application.
Quick example
function createGreeter(greeting) {
return function (name) {
console.log(`${greeting}, ${name}!`);
};
}
const sayHi = createGreeter('Hello');
const sayBye = createGreeter('Goodbye');
sayHi('Maria'); // Hello, Maria!
sayBye('Oleh'); // Goodbye, Oleh!Here createGreeter is the factory, while sayHi and sayBye are the functions it "produced". Each of them closes over its own greeting value.
Private state through closures
The most valuable property of a factory: every call creates a separate variable environment that only the returned function can reach.
function createCounter(start = 0) {
let count = start;
return function () {
count++;
return count;
};
}
const counterA = createCounter();
const counterB = createCounter(10);
console.log(counterA()); // 1
console.log(counterA()); // 2
console.log(counterB()); // 11counterA and counterB do not interfere with each other: each has its own count, and there is no way to reach it from the outside except through the returned function. That is encapsulation without classes and without this.
Configuring behaviour: filters
A factory lets you push what varies into a parameter and stop duplicating the rest of the code.
function createFilter(minValue) {
return function (numbers) {
return numbers.filter(n => n >= minValue);
};
}
const filterAbove10 = createFilter(10);
console.log(filterAbove10([5, 8, 13, 21])); // [13, 21]Partial application
When a factory fixes part of a future call's arguments, you get partial application, a close relative of currying.
function multiplyBy(factor) {
return function (number) {
return number * factor;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15It is a convenient way to build specialised functions on the fly and pass them to map, filter or event handlers.
A real case: a logger
function createLogger(prefix) {
return function (message) {
console.log(`[${prefix}] ${message}`);
};
}
const info = createLogger('INFO');
const error = createLogger('ERROR');
info('Server started'); // [INFO] Server started
error('Something went wrong'); // [ERROR] Something went wrongOne template, different functions for different contexts.
Why it is convenient
| Benefit | Description |
|---|---|
| Code reuse | The factory creates many similar functions without duplicating logic |
| Data encapsulation | It uses closures to hold internal state |
| Flexibility | You can create functions with dynamic behaviour |
| Functional style | Fits well with map, filter, reduce |
Summary table
| Property | Description |
|---|---|
| What it is | A function that returns another function |
| Core idea | "Generate" functions from a shared template |
| What it relies on | Closures |
| Where it is used | Configuration, handlers, loggers, counters, factory patterns |
| Benefits | Fewer repetitions, more flexibility and readability |
Common mistakes
- Confusing a factory function with an object factory: the first returns a function, the second returns an object (though both rest on the same idea).
- Moving the state outside the factory, for example into a module variable. Then every "produced" function shares one counter and the independence is gone.
- Creating a factory inside a render or a loop for no reason: a brand new function is born every time, which means extra allocations and broken memoisation in UI frameworks.
- Forgetting that a closure keeps a reference to everything it captured. If the factory captured a large object, it will not be freed while the returned function is alive.
- Building three levels of nesting where one extra argument would do: the code gets harder to read with no benefit at all.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.