Lesson 6 of 8
Currying & Partial Application
Transform functions to accept arguments one at a time for flexibility and reuse
What is Currying?
Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument. Named after mathematician Haskell Curry, it's fundamental to functional programming and enables powerful composition patterns.
// Regular function with multiple arguments
const add = (a, b, c) => a + b + c;
add(1, 2, 3); // 6
// Curried version
const addCurried = a => b => c => a + b + c;
addCurried(1)(2)(3); // 6
// The magic: partial application
const add1 = addCurried(1); // b => c => 1 + b + c
const add1and2 = add1(2); // c => 1 + 2 + c
const result = add1and2(3); // 6
// Or step by step
const addTo10 = addCurried(10);
const addTo10And5 = addTo10(5);
addTo10And5(3); // 18
Currying vs Partial Application
- Currying: Always produces unary (single-argument) functions. A function of n arguments becomes n functions of 1 argument.
- Partial Application: Fixes some arguments of a function, producing a function with fewer arguments. More flexible about how many arguments to fix.
// CURRYING: unary functions only
const curriedAdd = a => b => c => a + b + c;
curriedAdd(1)(2)(3); // Must call one arg at a time
// PARTIAL APPLICATION: fix any number of arguments
const add = (a, b, c) => a + b + c;
// Using bind for partial application
const add1 = add.bind(null, 1); // a=1 fixed
add1(2, 3); // 6
const add1and2 = add.bind(null, 1, 2); // a=1, b=2 fixed
add1and2(3); // 6
// Partial application helper
const partial = (fn, ...fixedArgs) =>
(...remainingArgs) => fn(...fixedArgs, ...remainingArgs);
const add5 = partial(add, 5);
add5(10, 20); // 35
Implementing curry
// Simple curry function
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...moreArgs) => curried(...args, ...moreArgs);
};
}
// Usage
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6 - flexible!
curriedAdd(1)(2, 3); // 6
curriedAdd(1, 2, 3); // 6
// Arrow function version
const curry = fn =>
function curried(...args) {
return args.length >= fn.length
? fn(...args)
: (...more) => curried(...args, ...more);
};
Practical Currying Examples
// 1. Configuration functions
const log = level => message =>
console.log(`[${level}] ${message}`);
const logError = log('ERROR');
const logInfo = log('INFO');
const logDebug = log('DEBUG');
logError('Something went wrong!');
logInfo('User logged in');
// 2. Event handlers
const handleEvent = eventType => handler => element => {
element.addEventListener(eventType, handler);
return () => element.removeEventListener(eventType, handler);
};
const onClick = handleEvent('click');
const onSubmit = handleEvent('submit');
const removeClickHandler = onClick(() => console.log('Clicked!'))(button);
// 3. API helpers
const api = baseUrl => endpoint => options =>
fetch(`${baseUrl}${endpoint}`, options).then(r => r.json());
const myApi = api('https://api.example.com');
const getUsers = myApi('/users');
const getPosts = myApi('/posts');
// Call with options
getUsers({ headers: { 'Authorization': 'Bearer token' } });
// 4. Data transformation
const map = fn => arr => arr.map(fn);
const filter = pred => arr => arr.filter(pred);
const prop = key => obj => obj[key];
const getNames = map(prop('name'));
const getActiveUsers = filter(prop('active'));
const users = [
{ name: 'Alice', active: true },
{ name: 'Bob', active: false },
];
getNames(users); // ['Alice', 'Bob']
getActiveUsers(users); // [{ name: 'Alice', active: true }]
Currying for Composition
// Curried functions compose beautifully
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
// Curried utilities
const map = fn => arr => arr.map(fn);
const filter = pred => arr => arr.filter(pred);
const sort = compareFn => arr => [...arr].sort(compareFn);
const take = n => arr => arr.slice(0, n);
const prop = key => obj => obj[key];
// Build a data processing pipeline
const processUsers = pipe(
filter(user => user.age >= 18),
sort((a, b) => a.name.localeCompare(b.name)),
take(5),
map(prop('name'))
);
const users = [
{ name: 'Charlie', age: 25 },
{ name: 'Alice', age: 17 },
{ name: 'Bob', age: 30 },
{ name: 'Diana', age: 22 },
];
processUsers(users); // ['Bob', 'Charlie', 'Diana']
// Compare to non-curried
users
.filter(user => user.age >= 18)
.sort((a, b) => a.name.localeCompare(b.name))
.slice(0, 5)
.map(user => user.name);
// The curried version is more reusable!
// processUsers can be used anywhere
Placeholder Currying
// Sometimes you want to fix arguments out of order
// Ramda uses R.__ as placeholder
// Simple placeholder implementation
const _ = Symbol('placeholder');
function curryWithPlaceholder(fn) {
return function curried(...args) {
const hasPlaceholder = args.some(arg => arg === _);
const complete = args.length >= fn.length && !hasPlaceholder;
if (complete) return fn(...args);
return (...more) => {
const merged = args.map(arg =>
arg === _ && more.length ? more.shift() : arg
);
return curried(...merged, ...more);
};
};
}
const divide = (a, b) => a / b;
const curriedDivide = curryWithPlaceholder(divide);
const divideBy2 = curriedDivide(_, 2); // Fix second arg
divideBy2(10); // 5
const divide10By = curriedDivide(10, _); // Fix first arg
divide10By(2); // 5
Currying in React
// Event handler factories
function TodoList({ todos, onToggle, onDelete }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={handleToggle(todo.id)}
/>
{todo.text}
<button onClick={handleDelete(todo.id)}>
Delete
</button>
</li>
))}
</ul>
);
// Curried handlers
function handleToggle(id) {
return () => onToggle(id);
}
function handleDelete(id) {
return () => onDelete(id);
}
}
// HOC pattern with currying
const withAuth = requiredRole => Component => props => {
const { user } = useAuth();
if (!user || user.role !== requiredRole) {
return <Navigate to="/login" />;
}
return <Component {...props} />;
};
const AdminDashboard = withAuth('admin')(Dashboard);
const UserProfile = withAuth('user')(Profile);
⚠️ When to Use Currying
- ✅ When you need to create specialized functions from general ones
- ✅ When composing functions in pipelines
- ✅ When creating event handler factories
- ✅ When building configuration-based functions
- ❌ Don't overcurry - sometimes explicit arguments are clearer
- ❌ Be careful with 'this' context in curried methods