Understanding JavaScript Currying with a practical use case

Most used functional programming design pattern

Mudafar El Halabi
4 min readApr 1, 2021

The problem

Given a comma separated values string, aka “.csv”, with numbers in decimal system — base 10. Get a parsed array of integers.

Sounds like a good Interview question!

const input = '1, 2, 3, 4, 5'

function parseCSVNumbers(csvString){
// some logic :)
}

const output = [1, 2, 3, 4, 5]

A common solution

  1. Split csv input string, into an array of strings.
  2. Parse each string from the array.
  3. Return an array with the parsed numbers.
function parseCSVNumbers(csvString) {
const stringNumbers = csvString.split(',')
const parsedNumbers = stringNumbers.map(stringNumber => parseInt(stringNumber))

return parsedNumbers
}

Interview score: not bad…

This is one of the most used and simple currying in everyday JavaScript codes— even though it’s not commonly recognized as so:

stringNumber => parseInt(stringNumber)

With this extra explanation, our score was improved to good.

In step to really understand why this is in fact a valid currying case, let’s rewrite this callback as an ordinary JavaScript function:

function oneParameterParseInt (string) { 
return parseInt(string)
}

The prefix oneParameter was added on purpose to emphasize what this function really does — calls JavaScript standard built-in object parseInt with strictly one parameter, and thus ensuring a radix/base of 10 parsing.

In other words, the arity of parseInt was reduced from 2 to 1.

Currying by hand solution

Instead of limiting parseInt to one parameter, let’s curry it properly:

function curriedParseInt(string) { 
return function(radix) {
return parseInt(string, radix)
}

First call of curriedParseInt which takes a string parameter, will return a function which takes a radix parameter, which finally will call parseInt with both parameters and return its result.

const parseOne = curriedParseInt('1')
const parsedNumber = parseOne(10)

or

const parsedNumber = curriedParseInt('1')(10)

Only our conceptual interview score was improved.

Well, is it possible to improve the solution using this curried version of parseInt ?

Short answer is no, because it’s still needed to explicitly provide the radix for each string value from the csv.

Invert parameters order

As the csv has the same radix for all its values, it’ll be useful to use the radix once and then to have a function to parse each value — a callback for the map.

This can be achieved by simply inverting the parameters order as follow:

function curryiedInvertedParseInt(radix) { 
return function(string) {
return parseInt(string, radix)
}

Similarly, first call of curriedInvertedParseInt which takes a radix parameter, will return a function which takes a string parameter, which finally will call parseInt with both parameters and return its result.

Finally it’s possible to improve the original solution as follow:

function parseCSVNumbers(csvString, base=10) {
const parse = radix => string => parseInt(string, radix)
const stringNumbers = csvString.split(',')
const parsedNumbers = stringNumbers.map(parse(base))

return parsedNumbers
}

or

function parseCSVNumbers(csvString, base=10) {
const parse = radix => string => parseInt(string, radix)
const parseDecimal = parse(base)
const stringNumbers = csvString.split(',')
const parsedNumbers = stringNumbers.map(parseDecimal)

return parsedNumbers
}

Our practical interview score has increased. Now we are very good!

Further improvements will require more functional base elements such as pipeline, compose or flow. Good news is there is a proposal for it https://github.com/tc39/proposal-pipeline-operator

A more generic currying solution

To reinforce currying concept, let’s code a generic function which takes any function and returns a curried version of it.

In the following recursive solution, bind is used — standard built-in function prototype method to set this and to add preceding parameters.

function curry(fn) {
if (fn.length === 0) {
return fn()
}
return function bindOneParam (p) {
return curry(fn.bind(null, p))
}
}

A recursive function is a function that calls itself during its execution.

It’s very important to have a stop condition in recursion, which in this case is to have no parameters left from fn — parameters length equal to 0.

This curry version will extract one parameter at a time from fn parameters, and return a new function which takes the extracted parameter as an input and binds it to fn and call curry again, but this time fn has one less parameter. Last call will simply return fn execution result.

Take your time to digest all these concepts.

Now it’s time to see how curry really works with the arity 2 — two arguments, parseInt standard function:

const stringParam = curry(parseInt)

as parseInt.length is not 0 , curry will return stringParam which is basically:

function stringParam (string) {
return curry(parseInt.bind(null, string))
}

Calling stringParam with a string parameter:

const radixParam = stringParam('1234')

Will first evaluate the inner bind function, which will extract one parameter from parseInt and return a new function with ‘1234’ as a preceding argument:

const innerBind = parseInt.bind(null, '1234')

Then curry(innerBind) , which is the first recursive call to curry , but this time with the arity 1 innerBind function instead of parseInt , this will similarly return:

function radixParam (radix) {
return curry(innerBind.bind(null, radix))
}

Analog to stringParam, calling radixParam with a radix parameter:

const decimalNumber = radixParam(10)

Will first evaluate the last bind function, which will extract one parameter from innerBind and return a new function with 10 as a preceding argument:

const lastBind = innerBind.bind(null, 10)

Then curry(lastBind) , the second recursive call to curry , but this time with the arity 0 lastBind function instead of innerBind , this will end the recursion, execute lastBind and return its value.

If you still here, congratulation, our interview score now is excellent!

Curring is commonly used in modern JavaScript code, at least in its simpliest form, and combining it with pipe allow for a clean, readible and powerful functional programming — even in JavaScript.

Last interview question, why can’t we simply do this, without currying at all: ['1', '2', '3', '4'].map(parseInt) ?

--

--