What Laziness Means in Haskell
Haskell is a non-strict language: an expression is not reduced to a value until something actually demands that value. GHC implements this using thunks -- unevaluated computation graphs that get forced (evaluated to weak head normal form) on first use and then cached so later uses reuse the same result. This differs from eager languages like Python or Java, where let x = f(y) runs f immediately regardless of whether x is ever read.
Cricket analogy: Like a third-umpire review that's only triggered and computed when the on-field umpire is unsure and the captain asks for it, a Haskell thunk for f y sits idle until x is actually read, unlike a scorer who logs every run the instant it happens.
Thunks, Sharing, and Space Leaks
Because a thunk is shared rather than recomputed, let total = sum [1..1000000] in (total, total) only sums the list once. The flip side is that an unevaluated thunk chain built up inside a lazy foldl can accumulate a huge pile of pending additions in memory instead of a single integer, which is the classic Haskell space leak. Replacing foldl with Data.List.foldl' (strict in the accumulator) or adding a BangPatterns !acc forces each intermediate result immediately and keeps memory flat.
Cricket analogy: Sharing a thunk is like a stadium scoreboard operator computing the run rate once and reusing it for both the giant screen and radio commentary, but a lazy foldl building up unevaluated addition chains is like letting a full over's run totals pile up unadded until the strategic timeout, causing a scramble at the end.
-- Lazy foldl builds up a chain of thunks: (((0+1)+2)+3)+...
sumLazy :: [Int] -> Int
sumLazy = foldl (+) 0
-- On a long list this can blow the stack/heap because the
-- additions are never forced until the very end.
import Data.List (foldl')
sumStrict :: [Int] -> Int
sumStrict = foldl' (+) 0
-- foldl' forces the accumulator at each step, keeping memory flat.
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
main :: IO ()
main = do
print (sumStrict [1..10000000]) -- runs in constant space
print (take 10 fibs) -- [0,1,1,2,3,5,8,13,21,34]
Infinite Data Structures
Laziness lets you write fibs = 0 : 1 : zipWith (+) fibs (tail fibs) or primes = sieve [2..] as genuinely infinite lists, because GHC only ever forces as many list cells as take 10 fibs or a bounded consumer actually asks for; the rest of the spine simply stays as an unforced thunk. This turns generate-and-filter algorithms into compositional one-liners that would require explicit termination conditions in a strict language.
Cricket analogy: A ball-by-ball radio commentary feed is conceptually infinite for the length of a Test match, but the listener only ever consumes the balls actually bowled so far, just as take 10 fibs only forces the first ten cells of an infinite Fibonacci list.
length xs forces the spine of the list (every cons cell) but not the elements themselves, so length (map undefined xs) succeeds even though every element is undefined -- don't assume length proves your list's contents are safe to use.
Controlling Strictness
GHC gives you seq, $!, BangPatterns (f !x = ...), and Control.DeepSeq's deepseq/force to opt back into eager evaluation when profiling shows a leak. seq a b forces a to weak head normal form before returning b, which is enough for primitive types but not for forcing every element of a nested structure, where deepseq is needed instead.
Cricket analogy: Calling seq is like a captain forcing an immediate DRS decision instead of letting it linger, but deepseq is like reviewing every camera angle and sensor fully before proceeding, forcing the entire nested structure, not just the top-level verdict.
GHC's strictness analyzer already makes many local bindings strict automatically when it can prove doing so is safe and won't change the program's termination behavior, so don't reach for seq or BangPatterns reflexively -- profile with +RTS -s or heap profiling first and only add strictness annotations where a real leak shows up.
- Haskell is non-strict: expressions become thunks that are only evaluated when their value is actually demanded.
- Evaluated thunks are shared/memoized, so a value used twice via
letis computed once, not twice. - Space leaks happen when thunks accumulate unevaluated (e.g., a lazy
foldlchain) instead of being forced incrementally. Data.List.foldl',BangPatterns,seq, anddeepseqlet you opt back into strict evaluation where it matters.- Laziness enables infinite data structures like
fibsandprimes, since only the demanded prefix is ever forced. seq/$!force to weak head normal form (WHNF) only;deepseqforces an entire nested structure.- GHC's strictness analysis already optimizes some cases automatically -- profile before manually adding strictness.
Practice what you learned
1. What does it mean for GHC to evaluate an expression to 'weak head normal form' (WHNF)?
2. Why is `foldl (+) 0 [1..1000000]` more likely to cause a stack/heap problem than `foldl' (+) 0 [1..1000000]`?
3. Why does `fibs = 0 : 1 : zipWith (+) fibs (tail fibs)` work in Haskell instead of looping forever?
4. What is the key difference between `seq` and `deepseq`?
5. Which statement about `length (map undefined [1,2,3])` is correct?
Was this page helpful?
You May Also Like
Error Handling in Haskell
Learn how Haskell represents failure explicitly using Maybe, Either, and exceptions, and when to reach for each approach.
Concurrency in Haskell
Learn how Haskell's lightweight threads, MVar, STM, and the async library make concurrent and parallel programming safer and more composable.
Testing Haskell Code
Learn how to write unit tests, property-based tests, and doctest examples for Haskell code using HUnit, Hspec, and QuickCheck.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics