Skip to content

Currying and Partial Application

Converting a function with multiple arguments into a sequence of functions, each taking a single argument.

// Regular function
function add(a: number, b: number): number {
return a + b;
}
add(2, 3); // 5
// Curried version
function curriedAdd(a: number) {
return (b: number): number => a + b;
}
const add2 = curriedAdd(2);
add2(3); // 5
add2(5); // 7
// Curry two-argument function
function curry<A, B, C>(
fn: (a: A, b: B) => C
): (a: A) => (b: B) => C {
return (a: A) => (b: B) => fn(a, b);
}
// Usage
const multiply = (a: number, b: number) => a * b;
const curriedMultiply = curry(multiply);
const triple = curriedMultiply(3);
triple(4); // 12
triple(5); // 15
// Or directly
curriedMultiply(3)(4); // 12
function curry3<A, B, C, D>(
fn: (a: A, b: B, c: C) => D
): (a: A) => (b: B) => (c: C) => D {
return (a: A) => (b: B) => (c: C) => fn(a, b, c);
}
// Usage
const sum3 = (a: number, b: number, c: number) => a + b + c;
const curriedSum = curry3(sum3);
curriedSum(1)(2)(3); // 6
const add1 = curriedSum(1);
const add1And2 = add1(2);
add1And2(3); // 6

Pre-filling some arguments of a function without transforming its arity.

function partial<A, B, C>(
fn: (a: A, b: B) => C,
a: A
): (b: B) => C {
return (b: B) => fn(a, b);
}
// Usage
function greet(greeting: string, name: string): string {
return `${greeting}, ${name}!`;
}
const sayHello = partial(greet, 'Hello');
const sayGoodbye = partial(greet, 'Goodbye');
sayHello('John'); // "Hello, John!"
sayHello('Jane'); // "Hello, Jane!"
sayGoodbye('Bob'); // "Goodbye, Bob!"
function partialRight<A, B, C>(
fn: (a: A, b: B) => C,
b: B
): (a: A) => C {
return (a: A) => fn(a, b);
}
// Usage
const divide = (a: number, b: number) => a / b;
const divideBy2 = partialRight(divide, 2);
divideBy2(10); // 5
divideBy2(20); // 10
// Without currying - repetitive
button1.addEventListener('click', () => handleClick('button1'));
button2.addEventListener('click', () => handleClick('button2'));
button3.addEventListener('click', () => handleClick('button3'));
// With currying - cleaner
const createClickHandler = (id: string) => () => handleClick(id);
button1.addEventListener('click', createClickHandler('button1'));
button2.addEventListener('click', createClickHandler('button2'));
button3.addEventListener('click', createClickHandler('button3'));
function apiRequest<T>(
method: string,
url: string,
data?: any
): Promise<T> {
return fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: data ? JSON.stringify(data) : undefined
}).then(r => r.json());
}
// Create specialized functions
const get = curry(apiRequest)('GET');
const post = curry(apiRequest)('POST');
const put = curry(apiRequest)('PUT');
const del = curry(apiRequest)('DELETE');
// Usage
await get('/api/users');
await post('/api/users', { name: 'John' });
await put('/api/users/1', { name: 'Jane' });
await del('/api/users/1');
function validate<T>(
predicate: (value: T) => boolean,
message: string,
value: T
): { valid: boolean; message?: string } {
return predicate(value)
? { valid: true }
: { valid: false, message };
}
// Create validators
const curriedValidate = curry3(validate);
const validateEmail = curriedValidate(
(email: string) => email.includes('@')
)('Invalid email format');
const validateLength = curriedValidate(
(str: string) => str.length >= 8
)('Minimum 8 characters required');
// Use validators
validateEmail('user@example.com'); // { valid: true }
validateEmail('invalid'); // { valid: false, message: '...' }
validateLength('password123'); // { valid: true }
validateLength('short'); // { valid: false, message: '...' }
interface LogConfig {
level: 'debug' | 'info' | 'warn' | 'error';
prefix?: string;
}
function log(config: LogConfig, message: string): void {
const prefix = config.prefix ? `[${config.prefix}]` : '';
console[config.level](`${prefix} ${message}`);
}
// Create specialized loggers
const createLogger = partial(log, { level: 'info', prefix: 'APP' });
const debugLogger = partial(log, { level: 'debug', prefix: 'DEBUG' });
const errorLogger = partial(log, { level: 'error', prefix: 'ERROR' });
createLogger('Application started');
debugLogger('Debug information');
errorLogger('Something went wrong');
function autoCurry<T extends any[], R>(
fn: (...args: T) => R,
...providedArgs: any[]
): any {
return providedArgs.length >= fn.length
? fn(...(providedArgs as T))
: (...args: any[]) => autoCurry(fn, ...providedArgs, ...args);
}
// Usage
const sum = (a: number, b: number, c: number) => a + b + c;
const curriedSum = autoCurry(sum);
curriedSum(1, 2, 3); // 6
curriedSum(1)(2)(3); // 6
curriedSum(1, 2)(3); // 6
curriedSum(1)(2, 3); // 6
const _ = Symbol('placeholder');
function curry2WithPlaceholder<A, B, C>(
fn: (a: A, b: B) => C
) {
return (a: A | typeof _, b?: B) => {
if (a === _ && b !== undefined) {
return (realA: A) => fn(realA, b);
}
if (b === undefined) {
return (realB: B) => fn(a as A, realB);
}
return fn(a as A, b);
};
}
// Usage
const subtract = (a: number, b: number) => a - b;
const curriedSubtract = curry2WithPlaceholder(subtract);
const subtract5 = curriedSubtract(_, 5);
subtract5(10); // 5
const subtractFrom10 = curriedSubtract(10);
subtractFrom10(3); // 7
function memoize<A, B>(fn: (a: A) => B): (a: A) => B {
const cache = new Map<A, B>();
return (a: A): B => {
if (cache.has(a)) {
return cache.get(a)!;
}
const result = fn(a);
cache.set(a, result);
return result;
};
}
// Combine with currying for efficient reuse
const expensiveCalculation = memoize(
curry((factor: number, value: number) => {
// Expensive computation
return factor * value * Math.random();
})
);
const calculate5x = expensiveCalculation(5);
calculate5x(10); // Cached for factor=5, value=10
calculate5x(10); // Returns cached result
// Currying - transforms function arity
const curriedAdd = curry((a: number, b: number) => a + b);
curriedAdd(2)(3); // Must provide one arg at a time
// Partial Application - fixes some arguments
const add2 = partial((a: number, b: number) => a + b, 2);
add2(3); // Provide remaining arguments at once

Use Currying When:

  • Building function pipelines
  • Creating specialized function factories
  • Working with functional libraries (Ramda, fp-ts)
  • Need maximum flexibility in argument application

Use Partial Application When:

  • Have fixed configuration or context
  • Creating callback functions
  • Simplifying function signatures
  • Need to bind some arguments but not all

✅ Use currying for function composition ✅ Create specialized functions with partial application ✅ Keep curried functions pure ✅ Use descriptive names for specialized functions ✅ Curry functions that are frequently reused ✅ Document curried function signatures ✅ Use with higher-order functions

❌ Over-curry simple functions ❌ Curry functions with many arguments (>3) ❌ Use currying for one-off functions ❌ Mix currying with mutable state ❌ Create confusing intermediate functions ❌ Forget about performance implications ❌ Sacrifice readability for currying