Programming Techniques

Continuation-Passing Style

Encoding control flow explicitly by passing the "rest of the computation" as a function argument.

Overview

Continuation-Passing Style (CPS) is a programming technique where instead of returning a value, a function receives an additional "continuation" argument, a function representing the rest of the computation, and calls it with the result. It makes control flow explicit and is the theoretical foundation of async callbacks and compilers.

Origin

CPS was formalized by Christopher Strachey and Christopher Wadsworth in the early 1970s. It is the internal form used by many compilers (Scheme, ML) to simplify analysis and transformation. It directly inspired the callback pattern in Node.js.

Examples

CPS transformation of a computation

// Direct style
function add(a, b) { return a + b }
function square(n) { return n * n }

const result = square(add(3, 4))  // 49

// CPS, each function receives a continuation
function addCPS(a, b, k) { k(a + b) }
function squareCPS(n, k) { k(n * n) }

addCPS(3, 4, sum =>
  squareCPS(sum, result =>
    console.log(result)  // 49
  )
)

// Node.js error-first callbacks follow CPS
fs.readFile('config.json', (err, data) => {
  if (err) return handleError(err)
  JSON.parse(data.toString(), (err, config) => {
    // ...
  })
})

Node's callback pattern is CPS with an error-first convention: the first argument is null on success or an Error on failure. async/await desugars to CPS internally.

Use Cases

  • 01Compilers converting recursive programs to iterative form (CPS eliminates stack use)
  • 02Implementing coroutines and generators
  • 03Async callback patterns (historically, before Promises)
  • 04Implementing backtracking (pass a failure continuation alongside a success continuation)

When Not to Use

  • //Directly in application code: Promises and async/await are CPS with better syntax
  • //When it produces deeply nested callbacks ("callback hell"), flatten with named functions or async/await

Technical Notes

  • async/await is syntactic sugar that the compiler converts to CPS or state-machine form
  • CPS transformation makes all calls tail calls, enabling tail-call optimisation in languages that support it
  • Trampolining (see Recursion) is closely related: each thunk is effectively a continuation