Delimited Continuations Guide

What are Delimited Continuations?

Examples · Further reading

1. Continuation = “the rest of the computation”

While evaluating an expression, the remaining work waiting for its value is called the continuation. For example, while computing 2 * 3 in 1 + (2 * 3), the continuation is “add 1 to whatever value comes back” (v => 1 + v).

2. How reset and shift work

reset(() => ...) establishes a delimiter (boundary). Calling shift(k => ...) inside it captures the computation from the shift site up to the nearest enclosing reset as a function k, and passes k to the handler.

If the handler returns without calling k, the rest of the computation inside reset is discarded.

Read about the basic example

3. Why “Delimited”?

Traditional undelimited call/cc (as in Scheme) captures the entire rest of the program, and invoking it jumps without returning to the caller. In contrast, shift / reset captures only up to reset, so k behaves like a normal composable function that returns a value (and can even be used to build call/cc itself).

4. Notes for this playground

  • Multi-shot and shared state: This playground runs in multi-shot mode, allowing the same continuation to be resumed multiple times. Only control state (execution frames up to reset) is duplicated; variables and objects created before shift are shared and never rolled back.
  • Experimental synchronous JS extension: Capturing is supported across normal synchronous JS calls, excluding async, generators, active iterators, and finally cleanup on discarded continuations.

Further reading on delimited continuations

Examples

Select a title to open that example in the editor.

Basics: Capture Boundaries, Abort, and Control Flow

  • 1. Basics: 1 + shift

    shift captures the rest of the computation up to the nearest reset (here 1 + [ ]) as a function k. Calling k(2) evaluates 1 + 2 = 3 and returns it to the handler, which multiplies by 10 to produce 30.

  • 2. Nested resets

    shift captures up to the innermost reset. With the inner reset in place, only the addition of 10 is abandoned. The outer computation adds 100 to the returned value of 7. Removing the inner reset gives 7.

  • 3. Skip the rest of a computation

    If the shift handler returns a value without calling k, the remaining computation inside reset is discarded and 42 becomes the result of reset.

  • 4. Trace the order of execution

    Logs 1–6 trace how control moves: shift transfers control to its handler, k(2) resumes from the shift site until the end of reset, and the result (3) returns to k(2) inside the handler.

Manipulating Continuations: Escape, Branch, Re-capture

  • 5. Resume a continuation outside reset

    Returning k directly from shift(k => k) carries the continuation outside reset so it can be invoked multiple times later (this playground runs in multi-shot mode).

  • 6. Branch a computation

    Calling k(2) and k(3) inside the same handler evaluates the remaining 1 + [ ] computation along two branches and adds their results (3 + 4 = 7).

  • 7. A counter with shared state

    Multi-shot duplicates control state (where execution paused), not variable bindings or heap objects. Because count was created before shift, every resumption shares and increments the same variable.

  • 8. Capture again after resuming

    A resumed continuation can call shift again. Calling first(2) binds the first operand to 2 and yields a second-stage continuation a, while first(5) yields an independent continuation b.

Applications

  • 9. Search for pairs with a given sum

    choose resumes the rest of the computation for each element in an array. Using choose for x and y explores all pairs without writing nested loops.

  • 10. Backtracking: four queens

    This example builds backtracking from choose and guard (a shift that drops a branch by not calling k). Because shared arrays are not rolled back on resumption, place passes a new array columns.concat([column]) to each branch.

  • 11. Same Fringe: lazy tree traversal

    A recursive walk returns each leaf together with its continuation. Comparing the two trees one leaf at a time stops at the first mismatch. In the second comparison, 2 and 9 differ, so the final leaf 3 is never visited.

  • 12. Probabilistic programming

    flip(p) resumes the same continuation for heads and tails and sums the results weighted by their probabilities. With two independent coins whose chances of heads are 0.25 and 0.75, this example computes the chance of exactly one head and the chance that the first coin landed heads given that exactly one did.

  • 13. Call/CC within an outer reset

    callCC emulates Scheme-style call/cc using shift inside an outer reset. Calling the passed continuation discards the caller’s remaining computation via shift(ignored => k(value)) and jumps straight back to the callCC site.

  • 14. Algebraic effects: state and logging

    perform suspends program and returns an effect request containing the operation, argument, and resume continuation; handle interprets each request in a loop and resumes via step.resume(response), decoupling logic from side effects. The handler updates state and records logs.