BangPatterns: When and How to Force Strict Evaluation in Haskell
BangPatterns let you force eager evaluation of arguments in Haskell. This guide shows why, how, and when to use them, with a concrete sum example, performance checks, and common pitfalls.
25 Jun 2026, 07:48 UTC

The Problem: Space Leaks in Recursive Functions
Haskell’s default lazy evaluation can be a blessing and a curse. While laziness saves work when values are never needed, it also creates thunks—deferred computations that accumulate when a function repeatedly builds up unevaluated expressions. In tight loops or large data structures, these thunks can grow to occupy significant memory, causing a space leak that slows or crashes a program.
Consider a naive recursive sum:
sumLazy :: [Int] -> Int
sumLazy [] = 0
sumLazy (x:xs) = x + sumLazy xs
When sumLazy processes a million‑element list, each + operation creates a new thunk that waits until the final result is demanded. The accumulated thunks can reach megabytes of memory before finally being evaluated.
BangPatterns: The Quick Fix
BangPatterns is a GHC language extension that lets you annotate function arguments or data fields with a bang (!) to force eager evaluation. Enabling it is a one‑liner:
{-# LANGUAGE BangPatterns #-}
Or via the compiler flag:
ghc -XBangPatterns MyModule.hs
Once enabled, a bang before a pattern forces the matched value to be evaluated to weak head normal form (WHNF) before the function body executes. For numeric types, WHNF is the same as normal form, so the value is fully computed.
A Worked Example: Strict Sum
Below is a strict version of sumLazy that uses a bang on the accumulator to prevent thunk buildup:
sumStrict :: [Int] -> Int
sumStrict = go 0
where
go !acc [] = acc
go !acc (x:xs) = go (acc + x) xs
Key points:
- The outer
go 0creates a helper that carries an accumulator. - The bang before
accforces it to be evaluated before each recursive call. - Because
acc + xis computed eagerly, no thunk representing the sum of the rest of the list is retained.
Why It Matters
With this strict version, the memory footprint stays bounded by the size of a few integers, even for very large lists. The compiler can also inline the function and optimize away the accumulator, resulting in a tight loop comparable to a C implementation.
How to Verify Performance
To confirm the benefit, run a side‑by‑side comparison:
- Write both
sumLazyandsumStrictin a moduleSumDemo.hs. - Compile with optimizations and size reporting:
- Run each function on a large list and record peak memory and time:
- Use GHC’s profiling tools for deeper insight:
- Unnecessary Evaluation: If a value might never be needed (e.g., a branch that is rarely taken), forcing it can waste CPU cycles.
- Lazy IO: Forcing a lazy I/O action can cause the action to run earlier, potentially breaking the intended sequencing of effects.
- Recursive Data Types: Adding strictness to a field that participates in a recursive structure can create infinite loops if the base case is not handled carefully.
- Compiler Inference: GHC often infers strictness automatically. Manual annotations may be redundant unless profiling reveals a leak.
- Forgetting to enable the extension: a bang in a pattern without
-XBangPatternswill cause a compile error. - Using bangs on complex data constructors that are already strict, leading to no performance gain but extra code noise.
- Mixing strict and lazy fields in the same record without a clear design, which can lead to subtle bugs where parts of the record are unexpectedly unevaluated.
- Over‑optimizing: applying bangs everywhere can degrade performance if the added work outweighs the benefit of avoiding thunks.
ghc -O2 -s SumDemo.hs
import System.CPUTime
import System.Mem
main = do
let big = [1..10^7]
print $ sumLazy big
print $ sumStrict big
ghc -O2 -prof -fprof-auto SumDemo.hs
./SumDemo +RTS -p
The -p flag generates a .prof file; open it to see that sumStrict creates far fewer heap allocations and a smaller maximum heap size.
Inspecting Core
To see the strictness annotations in the Core, compile with:
ghc -O2 -ddump-simpl SumDemo.hs
Search for the go function; the compiler will mark the accumulator as strict (e.g., go :: Int -> [Int] -> Int with a strictness flag).
When to Avoid BangPatterns
Common Mistakes
Takeaway
BangPatterns are a powerful, low‑cost tool to control evaluation order in Haskell. Use them to eliminate space leaks in tight loops or large data structures, but be mindful of the trade‑off between eager evaluation and potential wasted work. Profile first, then apply strictness only where a measurable benefit appears.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.