Diagnosing and Fixing Space Leaks from Lazy Accumulation in Haskell
Identify and eliminate space leaks caused by lazy accumulation in Haskell using profiling, Core inspection, and strictness annotations.
25 Mar 2026, 09:19 UTC

Recognizable condition
A Haskell program shows steadily increasing resident memory as it processes larger inputs, even though the algorithm should use constant or logarithmic space. The growth is often linear in the size of the input and persists after garbage collection.
Cause / diagnostic table
| Symptom | Typical source |
|---|---|
| Memory grows proportionally to input length | Unevaluated thunks in an accumulator (e.g., lazy foldr) |
Retainer set shows many THUNK objects | Lazy data structures built incrementally (lists, tuples) without forcing |
| IO actions appear in heap profile | Deferred evaluation of IO actions or unsafeInterleaveIO |
Ordered checks
Compile with profiling enabled:
ghc -O2 -prof -fprof-auto -rtsopts Main.hs. Run the program with a representative input size, e.g.,./Main +RTS -p -hc(requires read/write permission on the current directory).Examine the heap profile (
Main.hp) withhp2ps Main.hporghc-events show. Look for a large band labelledTHUNKorFUNthat grows with input size.If the retainer set points to a specific function, inspect its Core:
ghc -O2 -ddump-simpl -dsuppress-idinfo Main.hs. Search for the function name and see whether its arguments appear as lazy bindings (let x = ...) rather than strict ones.Add a strictness annotation or
seqto the suspect accumulator and re‑compile. Observe whether the thunk band shrinks in the next heap profile.
Fixes tied to findings
Replace a lazy right fold with a strict left fold: change
foldr f z xstofoldl' f z xs(importData.List).Add bang patterns to accumulator fields:
!accin function arguments or record fields, requiring theBangPatternsextension.For complex data, force full evaluation with
deepSeqfromControl.DeepSeq:deepSeq acc $ ....When state is threaded through many steps, consider the strict state monad (
Control.Monad.State.Strict) instead of the lazy variant.
Escalation criteria
If after applying the above fixes the heap profile still shows significant THUNK retention:
- Check for hidden laziness in libraries (e.g.,
mapMvsmapM_,traverse). - Consider whether the algorithm inherently builds a large intermediate structure; redesign to process data in a streaming fashion using
conduitorpipes. - Verify that changes have not altered semantics in the presence of exceptions; run property‑based tests (
QuickCheck) to confirm.
Practical verification
Create a small test case that feeds increasing input sizes (e.g., lists of length 10ⁿ). Run it with +RTS -s to see total memory allocated. After a fix, the allocated memory should plateau or grow sub‑linearly rather than linearly.
Limitations
Over‑strictness can eliminate beneficial sharing and degrade performance. Apply strictness only where profiling shows thunk buildup, and re‑benchmark to ensure runtime does not regress.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.