Recursion with Accumulators

Introduction to the Accumulator Pattern

In our previous lesson, we learned how to use plain recursion to process lists item by item. With plain recursion, a function calls itself until it reaches a base case, and then it builds up the final result step by step on the way back up.

However, in many programs, it is helpful to keep track of a "running result" as we move forward through the data. If you have ever written a loop in another programming language where you updated a variable at each step, you already know why this is useful. In Haskell, we do not have loops or variables that we can update. Instead, we use a concept called an accumulator.

An accumulator is simply an extra parameter that we pass to our recursive function. It carries our running total (or running structure) through each step of the recursion.

To use an accumulator cleanly, we follow a standard Haskell pattern:

  1. We write a top-level wrapper function to provide a "starting seed" (like starting a total at 0).
  2. We write a helper function inside a where block (traditionally named go) that does the actual recursive work and updates the accumulator.

This beginner version demonstrates the accumulator shape. For very large lists, Haskell programmers often use strict accumulation to avoid building delayed work, and we will discuss that later.

Let's look at how we can build functions using this pattern.

Example 1: Summing a List (sumList)

We will start by rewriting a function to sum a list of integers, but this time we will use an accumulator.

First, we define our top-level function. It takes a list of integers and returns an integer. Inside, it immediately calls a helper function named go, passing 0 as the starting value of our running total, along with the list xs.

Haskell
sumList :: [Int] -> Int
sumList xs = go 0 xs

Next, we need to define go. We use a where block to attach go directly to sumList so that it stays neatly hidden as an internal helper. go will take two parameters: our accumulator (an Int) and our list (a [Int]).

Haskell
sumList :: [Int] -> Int
sumList xs = go 0 xs
  where
    go :: Int -> [Int] -> Int

Now, let's add our base case to go. When the list is empty [], it means we have finished looking at all the numbers. Unlike plain recursion where an empty list might return 0, here our final answer is already stored in our accumulator. So, we simply return the accumulator acc.

Haskell
sumList :: [Int] -> Int
sumList xs = go 0 xs
  where
    go :: Int -> [Int] -> Int
    go acc [] = acc

Finally, we write the recursive case. When the list has a head y and a tail ys, we call go again. This time, we update the accumulator by adding the current item y to it (acc + y), and we pass the rest of the list ys to continue the process.

Here is the complete code, including a main function to print the result:

Haskell
sumList :: [Int] -> Int
sumList xs = go 0 xs
  where
    go :: Int -> [Int] -> Int
    go acc []     = acc
    go acc (y:ys) = go (acc + y) ys

main :: IO ()
main = print (sumList [1, 2, 3, 4])

When you run this code, it will output:

text
10

Notice how 0 becomes 1, then 3, then 6, and finally 10 as the accumulator is passed forward through each recursive call.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal