1 Overview and history
1.1 Definition and origin in Scheme
The operator call/cc (abbreviated from “call with current continuation”) was introduced in the Scheme programming language during the 1970s and became a standard feature in the Revised<sup>4</sup> Report on the Algorithmic Language Scheme (R4RS, 1991). It allows a program to capture its own execution state—the current continuation—at any point and treat that state as a first-class procedure. This design reflects Scheme’s philosophy of providing a minimal set of powerful primitive operations from which complex control structures can be built.
1.2 Relationship to continuation-passing style (CPS)
Continuation-passing style is a programming technique in which every function receives an explicit continuation argument representing the rest of the computation. Programs written in CPS make control flow fully explicit, and call/cc can be seen as a mechanism that automatically transforms a direct-style program into CPS at runtime. Indeed, the semantics of call/cc are often formally defined by translation into CPS, and many Scheme compilers use CPS as an intermediate representation.
1.3 Influence on other languages (e.g., Ruby, SML/NJ)
The idea of first-class continuations inspired similar features in other languages. Ruby provides callcc (now deprecated in favor of Fibers and Enumerators). Standard ML of New Jersey (SML/NJ) offers a library for first-class continuations. More broadly, call/cc influenced the development of delimited continuations and algebraic effects in languages such as Haskell (via Cont monad) and OCaml (via Effect handlers). However, many modern languages avoid full, undelimited continuations due to their implementation complexity and potential for confusion.
2 Semantics and behavior
2.1 What is a continuation?
A continuation represents the “rest of the computation” from a given point in a program. For example, in the expression (+ 1 (* 2 3)), when evaluating (* 2 3) the continuation is “take the result, add 1 to it, and return that value.” A continuation is essentially a snapshot of the call stack and the pending computations at the moment it is captured.
2.1.1 Capture point and escape procedures
When call/cc is invoked, it captures the continuation at its own call site. This captured continuation is an “escape procedure”: when called, it abandons whatever computation is currently in progress and restores the saved control state, effectively jumping back to the capture point. The continuation is often called an “escape procedure” because it can be used to leap out of nested computations.
2.2 How call/cc works step-by-step
call/cc takes a single function f as its argument. It captures the current continuation k (a procedure of one argument) and immediately calls f with k as its argument. The function f may then do anything: it may call k to return a value to the capture point, or it may ignore k and proceed normally. The return value of call/cc is the value passed to k (if k is invoked) or the value returned by f (if k is not invoked).
2.2.1 Example: simple escape
(call/cc (lambda (k)
(+ 1 (k 42))))
;; Returns 42, not 43.
Here, k is the continuation of the whole call/cc expression. Calling (k 42) immediately returns 42 as the result of the call/cc, ignoring the pending addition.
2.2.2 Example: re-entering a continuation
(define saved #f)
(+ 1 (call/cc (lambda (k)
(set! saved k)
(k 0))))
;; Returns 1.
;; Later, (saved 5) returns 6.
After the initial call, saved holds the continuation. Calling (saved 5) re-enters the captured continuation, effectively pretending that call/cc returned 5, and then the addition proceeds, yielding 6.
2.3 Multiple invocations and reentrancy
A captured continuation can be invoked multiple times. Each invocation restores the same captured state, allowing a form of time-travel or backtracking. This is a distinguishing feature: unlike exception handlers or setjmp/longjmp, a continuation can be called repeatedly, and its environment (including mutable variables) is restored at each invocation.
2.4 Interaction with dynamic extent and closures
In Scheme, the dynamic extent of a continuation is the period from when it is captured until it is invoked. If a continuation is stored in a data structure and invoked later, it “escapes” its original dynamic extent. This can cause subtle interactions with closures that reference local variables: when the continuation is invoked, those variables are restored to their state at the time of capture, even if they were later modified. This behavior is sometimes called “first-class continuations with full reentrancy.”
3 Usage patterns and idioms
3.1 Non-local exits (early return, break)
The most straightforward use of call/cc is to implement non-local exits, similar to return in C or break in loops. By capturing a continuation at an outer level, an inner computation can “jump out” early, returning a value directly.
3.2 Exception handling (simulating try-catch)
By capturing a continuation at the start of a protected block and storing it in an exception handler, one can simulate try-catch semantics. When an error occurs, the handler invokes the captured continuation with an error value, skipping the rest of the block.
3.3 Coroutines and generators
Coroutines (cooperative multitasking between symmetrical functions) can be built by capturing continuations at suspension points. Each coroutine saves its continuation when yielding, and the scheduler invokes the next coroutine’s continuation to resume it. Generators, which produce sequences lazily, are a special case of coroutines.
3.4 Backtracking (nondeterministic programming)
With multiple captured continuations stored on a stack, a program can implement depth-first search with backtracking. When a branch fails, the program invokes the most recent continuation to try the next alternative. This is the basis of the well-known amb (ambiguous) operator.
3.5 Cooperative multitasking (simple scheduler)
Using continuations as lightweight “threads,” a simple round-robin scheduler can be written: each task yields by capturing its own continuation and passing it to the scheduler, which later invokes the next task’s continuation. This was famously used in the earliest Scheme Web servers.
4 Implementation techniques
4.1 Representing continuations in the runtime
A continuation must capture the entire call stack (including local variables and execution pointer) at the point of capture. Two main approaches exist: stack copying and stackless models.
4.1.1 Stack copying vs. stackless implementations
In stack-copying implementations (e.g., many early Scheme systems), the entire runtime stack is copied into a heap-allocated structure when a continuation is captured. This can be expensive but straightforward. In stackless implementations (e.g., using CPS compilation or a segmented stack), the stack is never directly used; instead, each function call pushes a frame onto a heap-allocated “spaghetti stack.” Continuations are then simply references to a stack of frames, making capture cheap but execution slower due to heap allocation.
4.2 Compilation to CPS
Many Scheme compilers, such as the SML/NJ compiler and early versions of Chez Scheme, transform the entire program into continuation-passing style as an intermediate representation. In CPS, the continuation is always explicit, so call/cc becomes trivial: it is just a function that passes the current continuation to its argument. This transformation simplifies compilation and optimizations like inlining and tail-call elimination.
4.3 Efficiency considerations and the “stack explosion” problem
Repeatedly capturing continuations in a loop can lead to accumulation of saved stack frames, sometimes causing memory exhaustion (“stack explosion”). Because each capture copies the stack, deep recursion with many captures can be prohibitively slow. Implementations often use heuristics to share stack segments or rely on CPS to avoid copying.
4.4 Tail-call optimization and call/cc
Tail-call optimization (TCO) is essential for efficient use of call/cc, because many control idioms (e.g., loops via tail recursion) rely on constant stack space. When a continuation is captured inside a tail-recursive loop, TCO ensures that the captured continuation does not hold onto unnecessary stack frames. Most Scheme systems that support call/cc also guarantee tail-call optimization.
5 Practical considerations
5.1 Common pitfalls and misuses
A frequent mistake is to confuse call/cc with call/ec (escape continuations) or to assume that continuations behave like exceptions. Another pitfall is capturing a continuation inside a closure that outlives its enclosing context, leading to dangling references. Also, using call/cc in performance-critical code without understanding the implementation cost can cause slowdowns.
5.1.1 Continuation escape and dangling references
If a continuation is captured and then stored in a global variable, it may be invoked after the original context has been garbage-collected. In Scheme, this is valid because the continuation holds references to its environment; however, it can lead to unexpected behavior if the programmer forgot that the continuation still exists.
5.2 Debugging and reasoning about control flow
Programs that use call/cc heavily can become difficult to debug because the flow of control jumps between contexts. Stack traces may be misleading, and breakpoints placed at the capture point may be triggered multiple times. Some Scheme implementations provide partial support for tracing continuations, but reasoning about non-local exits and re-entrancy remains challenging.
5.3 Compatibility with modern language features (e.g., delimited continuations)
Many language designers consider undelimited continuations (like call/cc) too powerful and replace them with delimited continuations (shift/reset). Delimited continuations restrict the captured portion to a user-defined scope, making them more predictable and efficient. Modern Scheme dialects (e.g., Racket) often deprecate call/cc in favor of delimited control abstractions while still providing it for backward compatibility.
6 Related concepts and alternatives
6.1 Delimited continuations (shift/reset, prompt/control)
Delimited continuations capture only a portion of the continuation, bounded by a “prompt” or delimiter. The operators shift and reset (also prompt and control) allow more modular control and are easier to compose. They are used in algebraic effects and effect handlers.
6.2 Other control operators: call/ec, let/cc
call/ec (call with escape continuation) is a restricted variant that captures a continuation that can be invoked only once. This simplifies implementation and reasoning. let/cc is a syntactic sugar that binds the continuation to a local variable without explicit lambda. Many Scheme implementations provide these as alternatives.
6.3 Continuation passing style as an alternative
Instead of using call/cc, programmers can write code explicitly in continuation-passing style. This makes control flow explicit and avoids runtime overhead, but it can complicate code readability. Some DSLs (e.g., for concurrency) are compiled to CPS automatically.
6.4 Languages with first-class continuations without call/cc (e.g., Ruby’s callcc)
Ruby’s callcc (provided by the Continuation class) offers similar functionality, though it is considered a relic and is not recommended for new code. Other languages, such as Python (via generators) and JavaScript (via async/await), provide coroutine-like control without exposing full continuations.
7 References and further reading
7.1 Original Scheme reports (R4RS, R5RS, R7RS)
- *Revised<sup>4</sup> Report on the Algorithmic Language Scheme* (1991)
- *Revised<sup>5</sup> Report on the Algorithmic Language Scheme* (1998)
- *Revised<sup>7</sup> Report on the Algorithmic Language Scheme* (2013)
7.2 Classic papers (e.g., “Call with Current Continuation Patterns” by Haynes et al.)
- Haynes, C. T., Friedman, D. P., & Wand, M. (1984). *Continuations and Coroutines*.
- Dybvig, R. K. (1998). *The Scheme Programming Language* (Chapter 9 on continuations).
- Flatt, M., & PLT. *The Racket Guide* (Section on continuations).
7.3 Online tutorials and implementations
- “Call with Current Continuation” – Scheme Wiki.
- “Continuations and Delimited Control” – community.schemewiki.org.
- Source code of Chez Scheme and Racket for implementation examples.