Explorer
JavaScript

currying

JavaScript Theory & Concepts

Currying in JavaScript

Transforming multi-argument functions into unary pipelines for ultimate reusability.

📖 The Story & Real-World Analogy

The Custom Sandwich Assembly Station

"Instead of a fast-food counter where you must yell your entire complex meal order all in one breath (`order("wheat", "turkey", "swiss", "mustard")`), think of a bespoke sandwich station. The chef asks: "What bread?" (`order("wheat")`). Once chosen, the chef hands you a station for fillings (`("turkey")`), then cheese (`("swiss")`), then sauces. This lets the restaurant prepare a pre-made "Wheat & Turkey Base" that can be completed with different cheeses for dozens of customers without re-specifying the bread and turkey every time!"

Currying transforms a function with multiple arguments $f(a, b, c)$ into a sequence of nesting unary functions f(a)(b)(c). It enables partial application, dependency injection, and cleaner function composition.

⚙️ How It Works Under The Hood (Step-by-Step)
1

Function Arity

Arity is the number of arguments a function expects, accessible via `fn.length`. Currying breaks down an N-ary function into N unary functions.

2

Closure Retention of Arguments

Each intermediate function captures previous arguments in its closure until all required arguments are collected.

3

Partial Application vs Currying

Currying converts to unary (one-argument) functions in sequence. Partial application fixes a subset of arguments and returns a function that takes the rest all at once.

4

Generic Curry Implementation

A recursive helper compares the count of accumulated arguments against `fn.length`. If enough are received, it executes; otherwise, it returns a new function waiting for more.

💻 Interactive Code Walkthrough

Building a generic, production-ready curry function and real-world logger:

JAVASCRIPT
// Generic recursive curry implementation
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...nextArgs) {
      return curried.apply(this, args.concat(nextArgs));
    };
  };
}

// Practical Example: Reusable Configurable Logger
const log = curry((date, level, message) => {
  console.log(`[${date.toISOString().slice(11, 19)}] [${level.toUpperCase()}] ${message}`);
});

const logToday = log(new Date());
const logErrorToday = logToday("error");
const logInfoToday = logToday("info");

logErrorToday("Database connection timed out");
logInfoToday("Server listening on port 3000");
Console Output:
CODE
[15:00:00] [ERROR] Database connection timed out
[15:00:00] [INFO] Server listening on port 3000
⚠️ Common Pitfalls & Interview Traps
Trap
Functions with Default Parameters Breaking fn.length

The Risk: In JavaScript, default parameters do not count towards `fn.length` (`((a, b = 1) => {}).length` is 1), causing naive curry utilities to execute prematurely.

The Fix: Ensure functions to be curried have explicit arity without default params, or specify arity explicitly to the curry helper.

⚡ 30-Second Quick Revision Cheat Sheet (TL;DR)
  • ✓ Currying transforms $f(a, b, c)$ into f(a)(b)(c).
  • ✓ Arity = number of expected parameters (`fn.length`).
  • ✓ Enables Partial Application and reusable function configuration.
  • ✓ Relies heavily on closures to accumulate arguments across calls.
  • ✓ Check `args.length >= fn.length` in generic curry utilities.

Finished this lesson?

Mark this chapter complete to update your learning streak and unlock the next lesson.