Learning Objectives

  • Understand what currying is and why it's useful
  • Transform regular functions into curried functions
  • Build reusable function factories with currying
  • Apply currying in real-world scenarios
  • Know when to use currying vs regular functions

What is Currying?

Currying is a functional programming technique that transforms a function with multiple arguments into a sequence of functions, each taking a single argument. Named after mathematician Haskell Curry, it's a powerful pattern for creating flexible, reusable code.

// Regular function with multiple arguments
function add(a, b, c) {
    return a + b + c;
}

add(1, 2, 3); // 6

// Curried version
function addCurried(a) {
    return function(b) {
        return function(c) {
            return a + b + c;
        };
    };
}

addCurried(1)(2)(3); // 6

The Basic Transformation

Let's see how a simple two-argument function transforms into a curried function:

// Before: Regular function
function multiply(x, y) {
    return x * y;
}

multiply(3, 4); // 12

// After: Curried function
function multiplyCurried(x) {
    return function(y) {
        return x * y;
    };
}

multiplyCurried(3)(4); // 12

// Or with arrow functions (more concise)
const multiplyCurried = x => y => x * y;

multiplyCurried(3)(4); // 12

Why Use Currying?

1. Partial Application

Create specialized functions by pre-filling some arguments:

const multiply = x => y => x * y;

// Create specialized functions
const double = multiply(2);
const triple = multiply(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

// Reuse them
console.log(double(10)); // 20
console.log(triple(10)); // 30

2. Function Composition

Curried functions compose beautifully:

const add = x => y => x + y;
const multiply = x => y => x * y;

// Create a pipeline
const addThenDouble = x => multiply(2)(add(5)(x));

console.log(addThenDouble(3)); // (3 + 5) * 2 = 16

3. Configuration and Reusability

Configure functions once, use them many times:

const greet = greeting => name => `${greeting}, ${name}!`;

// Configure different greetings
const sayHello = greet('Hello');
const sayHi = greet('Hi');
const sayGoodbye = greet('Goodbye');

console.log(sayHello('Alice'));   // "Hello, Alice!"
console.log(sayHi('Bob'));        // "Hi, Bob!"
console.log(sayGoodbye('Charlie')); // "Goodbye, Charlie!"

Real-World Example: Logger

// Curried logger function
const log = level => message => timestamp => {
    console.log(`[${timestamp}] ${level}: ${message}`);
};

// Create specialized loggers
const errorLog = log('ERROR');
const infoLog = log('INFO');
const debugLog = log('DEBUG');

// Use them
const now = new Date().toISOString();
errorLog('Database connection failed')(now);
infoLog('User logged in')(now);
debugLog('Processing request')(now);

// Or create even more specialized versions
const errorNow = errorLog('Something went wrong');
errorNow(new Date().toISOString());
errorNow(new Date().toISOString()); // Can reuse!

Real-World Example: API Request Builder

const apiRequest = method => endpoint => data => {
    return fetch(endpoint, {
        method: method,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
    });
};

// Create method-specific functions
const get = apiRequest('GET');
const post = apiRequest('POST');
const put = apiRequest('PUT');
const del = apiRequest('DELETE');

// Create endpoint-specific functions
const getUsers = get('/api/users');
const postUser = post('/api/users');
const updateUser = put('/api/users');

// Use them
getUsers(null).then(res => res.json());
postUser({ name: 'Alice', email: 'alice@example.com' });
updateUser({ id: 1, name: 'Alice Updated' });

Real-World Example: Discount Calculator

const applyDiscount = discountPercent => price => {
    return price - (price * discountPercent / 100);
};

// Create different discount functions
const apply10Percent = applyDiscount(10);
const apply20Percent = applyDiscount(20);
const apply50Percent = applyDiscount(50);

// Use them
console.log(apply10Percent(100)); // 90
console.log(apply20Percent(100)); // 80
console.log(apply50Percent(100)); // 50

// Apply to multiple items
const prices = [100, 200, 300];
const discountedPrices = prices.map(apply20Percent);
console.log(discountedPrices); // [80, 160, 240]

Currying with More Arguments

// Three arguments
const formatDate = year => month => day => {
    return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
};

console.log(formatDate(2024)(3)(15)); // "2024-03-15"

// Partially apply
const format2024 = formatDate(2024);
const format2024March = format2024(3);

console.log(format2024March(1));  // "2024-03-01"
console.log(format2024March(15)); // "2024-03-15"
console.log(format2024March(31)); // "2024-03-31"

// Four arguments
const createUser = firstName => lastName => email => age => ({
    firstName,
    lastName,
    email,
    age,
    fullName: `${firstName} ${lastName}`
});

const createSmith = createUser('John')('Smith');
const johnSmithGmail = createSmith('john.smith@gmail.com');

console.log(johnSmithGmail(30));
// { firstName: 'John', lastName: 'Smith', email: '...', age: 30, fullName: 'John Smith' }

Currying vs Regular Functions

// Regular function - must provide all arguments at once
function calculateTotal(price, quantity, taxRate) {
    return price * quantity * (1 + taxRate);
}

calculateTotal(10, 5, 0.08); // Must provide all three

// Curried version - flexible application
const calculateTotalCurried = price => quantity => taxRate => {
    return price * quantity * (1 + taxRate);
};

// Can apply arguments one at a time
const priceSet = calculateTotalCurried(10);
const quantitySet = priceSet(5);
const total = quantitySet(0.08);

// Or create reusable configurations
const calculateWithTax = calculateTotalCurried(10)(5);
console.log(calculateWithTax(0.08)); // 8% tax
console.log(calculateWithTax(0.10)); // 10% tax
console.log(calculateWithTax(0.15)); // 15% tax

When to Use Currying

  • Configuration: When you need to configure a function once and use it multiple times
  • Partial Application: When you want to create specialized versions of functions
  • Function Composition: When building pipelines of operations
  • Event Handlers: When you need to pass data to event handlers
  • Higher-Order Functions: When working with map, filter, reduce, etc.

Key Takeaways

  • ✅ Currying transforms f(a, b, c) into f(a)(b)(c)
  • ✅ Each curried function returns another function until all arguments are provided
  • ✅ Arrow functions make currying syntax concise: a => b => c => result
  • ✅ Currying enables partial application and function reuse
  • ✅ Use currying for configuration, composition, and specialization

Conclusion

You've learned the fundamentals of currying in JavaScript! Currying is a powerful technique for creating reusable, composable functions. Start applying it in your projects where you need configuration, partial application, or function composition.

Practice tip: Try converting some of your existing multi-parameter functions into curried versions and see where it improves code reusability.