Skip to content

Recursion

A function solves smaller instances of its own problem. Trace a recursive portfolio total, check its base case and progress, and distinguish call depth from repeated work.

Recursion is a method of solving a problem by having a function call itself, directly or through other functions, on smaller or simpler subproblems. A base case supplies an answer without another recursive call. In quant work, this can express the structure of a pricing tree or a nested portfolio clearly, but the design must control both repeated work and call depth.

Follow the calls, then the answers

Consider two fictional position values, 200 USD and 150 USD, measured at the same valuation time. To total them recursively, add the current position to the total of the positions still to come. When no positions remain, return zero.

This Python example advances an index through one list rather than copying a shorter list at each call:
def recursive_total(values: list[int]) -> int: """Sum an unchanged finite list of integer amounts.""" def subtotal(i: int) -> int: if i == len(values): return 0 return values[i] + subtotal(i + 1) return subtotal(0) position_values = [200, 150] # fictional USD values at one valuation time print(recursive_total(position_values)) # 350 print(recursive_total([])) # 0
The first call cannot finish 200 + subtotal(1) until the next call returns. That next call waits for subtotal(2), which reaches the base case and returns zero. The answers then travel back: 150 + 0 = 150 USD, followed by 200 + 150 = 350 USD. Each call has its own local i; all refer to the same unchanged list. Python's function tutorial explains how recursive calls receive separate local symbol tables.
For fictional position values [200, 150] USD, subtotal calls descend through indices 0, 1 and 2. Index 2 returns 0; index 1 returns 150; index 0 returns 350. The three subtotal frames coexist at the base case.
For fictional position values [200, 150] USD, subtotal calls descend through indices 0, 1 and 2. Index 2 returns 0; index 1 returns 150; index 0 returns 350. The three subtotal frames coexist at the base case.
Three calls to subtotal solve the two-position example. Calls move toward the empty remainder; returns complete the waiting additions in reverse order. Amounts are fictional USD values at one valuation time.

A base case is only part of the argument

For this function, the number of unprocessed items is len(values) - i. It starts at a nonnegative integer, falls by one on every recursive call, and reaches zero. This establishes termination for a finite, unchanged input list in a model with enough call capacity. Empty input is already at the base case.
A function can contain a base case and still fail to reach it. Replacing subtotal(i + 1) with subtotal(i) would repeat the same subproblem forever in that mathematical model; Python would eventually raise RecursionError. Increasing the index by two would also be wrong: it skips items and can miss the exact equality check for some input lengths. Check the progress rule against the full input domain, not just one successful example.
The example assumes integer amounts and does not validate a market-data schema. Its type annotations do not enforce that assumption at runtime. Missing values, mixed currencies and inconsistent valuation times need explicit policies before any aggregation method is meaningful.

Depth and repeated work are separate costs

Python retains the waiting calls in execution frames. For nn items, this implementation makes n+1n+1 calls to subtotal, with all those frames active when the base case is reached. Under a model treating indexing, integer addition and each frame as constant cost, it takes O(n)O(n) time and O(n)O(n) extra call-stack space. Python integers can grow, so that model abstracts away the cost of increasingly large integers. The input list is shared; there is no copied tail in this implementation.
Writing the recursive step with values[1:] instead would allocate a new list on each call. Across a list of length nn, those tail copies contain (n−1)+(n−2)+⋯+0(n-1)+(n-2)+\cdots+0 element references in total: quadratic copying work. That is a property of that implementation, not of recursion itself. Python's list tutorial documents that list slicing creates a new list.
Even correct progress does not guarantee a successful Python run for a large input. The interpreter has a recursion-depth limit, and exceeding it raises RecursionError. The usable depth depends on the current calling context and configuration; do not treat one observed limit as a portable maximum list length. Raising the limit does not fix a missing progress step and can be unsafe if set too high.
For a flat list of integer amounts, ordinary iteration or Python's sum expresses the calculation more simply and avoids one Python call frame per item. Recursion earns its place when the problem itself branches or nests. In a recombining pricing tree, several call paths can reach the same node: recursive code that recalculates that node on every visit wastes work. Memoisation stores completed answers; dynamic programming can organise the calculation so each required state is solved once. Caching repeated answers and limiting call depth solve different problems.
For more tracing examples, Recursion as an Alternative Control Mechanism develops factorial, Fibonacci and palindrome checking. Its public page is a preview; the full lesson requires membership.

Sources

Example executed with Python 3.14.4; official documentation consulted was Python 3.14.10.

Last updated
  • python
  • recursion
  • algorithms
  • call stack
  • termination
← Back to the glossaryBack to top ↑