Controlling Space Leaks in Haskell with BangPatterns
Learn how Haskell’s BangPatterns turn costly thunks into immediate values, eliminating common space leaks with a strict accumulator example.
13 Sept 2025, 17:08 UTC

Why lazy evaluation can hurt memory usage
Haskell’s non‑strict semantics means that expressions are turned into thunks (unevaluated computations) until their results are actually needed. When a function repeatedly builds new thunks without forcing them, the thunk chain can grow linearly with the input size, leading to a space leak. A classic example is a left‑fold that accumulates a sum:
sumLazy :: [Int] -> Int
sumLazy = foldl (+) 0
Each step creates a new thunk for the accumulator; the list is consumed lazily, but the accumulator stays unevaluated until the very end, causing O(n) heap usage.
How BangPatterns change evaluation
The BangPatterns extension lets you place a bang (!) in a pattern to force evaluation of that sub‑expression at the point of binding. When the pattern matches, the value is reduced to weak head normal form before the body of the function runs.
Applying this to the accumulator turns the fold into a strict left‑fold:
{-# LANGUAGE BangPatterns #-}
sumStrict :: [Int] -> Int
sumStrict = go 0
where
go !acc [] = acc
go !acc (x:xs) = go (acc + x) xs
The bang on acc forces the accumulator to be evaluated after each addition, so no thunk builds up.
Worked example: comparing the two versions
Consider a program that sums the numbers from 1 to 10 million.
main :: IO ()
main = print (sumLazy [1..10000000])
Compile with ghc -O2 SumLazy.hs and run with +RTS -s. You will see a large amount of memory allocated and a noticeable increase in residency, reflecting the thunk chain.
Now replace sumLazy with sumStrict (remember to add -XBangPatterns to the command line or pragma). The same runtime flags will show:
- Heap allocation close to the size of the input list (only the list itself).
- Maximum residency staying in the low kilobyte range, indicating constant‑space execution.
These observations match the expected behavior: the strict version never accumulates unevaluated additions.
Trade‑offs and limitations
While BangPatterns eliminate certain space leaks, they also remove laziness that might be useful:
- Infinite data structures can no longer be processed if the forced value depends on the whole structure (e.g.,
sumStrict [1..]diverges). - Over‑strictness can hinder shortcuts like early termination in
foldr-based algorithms, potentially slowing them down. - Readability suffers when many bangs clutter the code; the intent becomes less obvious to newcomers.
Therefore, apply BangPatterns only after profiling confirms a leak. GHC’s heap profiler (+RTS -hp) and the runtime statistics (+RTS -s) are the standard tools to verify that thunks are not accumulating.
Practical guidance for adoption
- Identify a suspect function: look for accumulators in
foldl, recursive parameters that grow, or large intermediate structures. - Add
{-# LANGUAGE BangPatterns #-}at the top of the module. - Place a bang on the parameter you want to force (usually the accumulator).
- Re‑compile with optimisation (
-O2) and run+RTS -sto compare allocation before and after. - If the heap profile shows a flat line (no growth), the change likely resolved the leak. If performance degrades, reconsider the strictness or explore alternatives like
foldl'fromData.List.
BangPatterns are a lightweight, local way to regain control over evaluation order without rewriting entire algorithms with explicit strictness annotations. Use them judiciously, guided by profiling data, and you can keep both the clarity of Haskell’s high‑level style and the predictable memory behavior required for production workloads.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.