DMarlon Studio

Lesson 10 · 10 of 20

Currying and memoization · JavaScript

Fix arguments step by step with currying and skip repeated work with memoization in JavaScript, using prices and Fibonacci.

JavaScript · Currying and memoization in DMarlon Studio: the example's code and its output in the terminal

Currying turns a function of several arguments into a chain of one-argument functions. price takes the VAT rate and returns a function that takes the discount, which in turn returns one that takes the net price. The payoff is partial application: spain already has 21% baked in and spainSale adds a 10% discount, ready to reuse.

curry turns any ordinary function into a curried one. It relies on fn.length, the number of parameters the function declares: while arguments are missing, it returns another function that collects more; once it has them all, it calls the original. That is why shipping can be called in several ways: toEurope(3)(true), toEurope(1, false) or with all three arguments at once.

Memoization stores a function's result for each argument and, when that argument comes back, returns it without recomputing. memoize keeps a Map as its cache inside a closure. It only works for pure functions, which always return the same output for the same input and have no side effects.

Fibonacci shows the difference: the plain recursive version makes 242,785 calls to compute fib(25), because it repeats the same work over and over. The memoized one needs only 26 (one for each number from 0 to 25), and none at all the second time. fastFib calls itself through the memoized version, so the recursion benefits from the cache too.

Code · main.js

// Currying: one function per argument, each returning the next one.const price = (vat) => (discount) => (net) => +(net * (1 - discount) * (1 + vat)).toFixed(2);const spain = price(0.21);const spainSale = spain(0.1);console.log("Sale prices:", spainSale(100), spainSale(250), "| full price:", spain(0)(100));// A generic curry helper: waits until it has every parameter.function curry(fn) {  return function curried(...args) {    return args.length >= fn.length ? fn(...args) : (...more) => curried(...args, ...more);  };}const shipping = curry((zone, kilos, express) => (zone === "EU" ? 5 : 12) + kilos * 2 + (express ? 10 : 0));const toEurope = shipping("EU");console.log("EU 3 kg express:", toEurope(3)(true), "| EU 1 kg:", toEurope(1, false));console.log("World 2 kg:", shipping("World", 2, false));// Memoization: remember each result so it is computed only once.function memoize(fn) {  const cache = new Map();  return (n) => {    if (!cache.has(n)) cache.set(n, fn(n));    return cache.get(n);  };}let calls = 0;const slowFib = (n) => (calls++, n < 2 ? n : slowFib(n - 1) + slowFib(n - 2));console.log("fib(25) =", slowFib(25), "in", calls, "calls");calls = 0;const fastFib = memoize((n) => (calls++, n < 2 ? n : fastFib(n - 1) + fastFib(n - 2)));console.log("fib(25) =", fastFib(25), "in", calls, "calls (memoized)");console.log("fib(25) again:", fastFib(25), "- still", calls, "calls");

Output · node main.js

Sale prices: 108.9 272.25 | full price: 121
EU 3 kg express: 21 | EU 1 kg: 7
World 2 kg: 16
fib(25) = 75025 in 242785 calls
fib(25) = 75025 in 26 calls (memoized)
fib(25) again: 75025 - still 26 calls

Key points

  • Currying turns f(a, b, c) into f(a)(b)(c).
  • Partial application creates specialised functions from a general one.
  • fn.length tells you how many parameters a function declares.
  • Memoizing caches results, and is only safe for pure functions.

Exercise

Use curry on a formatLine(currency, decimals, amount) function and create euros = formatLine("EUR")(2); then memoize a factorial function and count how many times it actually runs.