The two terms, plainly
Partial application: you pre-fill some arguments of a function and get back a function that takes the rest.
Currying: you turn f(a, b, c) into f(a)(b)(c). Every call takes exactly one argument.
In practice I rarely reach for full currying. Partial application is what I use daily.
Configured loggers
function log(level, context, message) {
console.log(`[${level}] [${context}] ${message}`);
}
const authLog = log.bind(null, 'INFO', 'auth');
authLog('user logged in');
authLog('token refreshed');
bind is partial application built into the language. Pass null as thisArg and pre-fill arguments. No library needed.
Handler factories
In React and DOM code I use partial application to avoid rebuilding handlers inline:
const remove = (id) => () => setItems((prev) => prev.filter((x) => x.id !== id));
<button onClick={remove(item.id)}>Remove</button>
remove(item.id) returns a function closed over id. Each button gets its own handler, no onClick={() => remove(item.id)} wrapper. Slightly cleaner, identical behavior.
Express/Koa middleware
Middleware factories are partial application in disguise:
function requireRole(role) {
return (req, res, next) => {
if (req.user?.role !== role) return res.sendStatus(403);
next();
};
}
app.get('/admin', requireRole('admin'), adminHandler);
You partially apply role, get back a middleware that closes over it. This is the default pattern for configurable middleware in the Node ecosystem.
When currying specifically helps
True currying shines when you want to build a pipeline and every step takes one argument:
import { curry } from 'lodash';
const filter = curry((pred, arr) => arr.filter(pred));
const map = curry((fn, arr) => arr.map(fn));
const activeNames = pipe(
filter((u) => u.active),
map((u) => u.name),
);
activeNames(users);
The data flows through at the end. Each step is reusable. This is the functional style that libraries like Ramda optimize for.
The TypeScript consideration
Currying plays poorly with overloads and default generics. A function like (a: number, b: string) => boolean has a clean type. Its curried form (a: number) => (b: string) => boolean is fine too, but stacked generics across layers can trip up inference. I curry rarely and explicitly in TypeScript, and reach for partial application via closures when I want flexibility.
The payoff
Partial application turns "a function that needs config and data" into two calls — configure once, use many times. That is the whole of the idea, and it quietly cleans up a lot of otherwise repetitive code.
