# Meaning of strictness

**URL:** <https://discourse.haskell.org/t/meaning-of-strictness/14326>\
**Category:** Learn\
**Created:** [June 28, 2026, 6:17am UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326 "2026-06-28T06:17:28Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![ynishiza](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ynishiza/32/4707_2.png) [@ynishiza](https://discourse.haskell.org/u/ynishiza)\
**Post date:** [June 28, 2026, 6:17am UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/1 "2026-06-28T06:17:28Z")

</div>

Hello Haskell community. I’ve only been a spectator in this discourse so far, so this is my first time starting a post myself. Nice to meet you!

I thought I understood what strictness meant but I realized there were still some holes when I tried to put it into words more precisely.  
I’m still a bit confused, so perhaps my formulation below may not be precise to begin with, but I’ll try my best.

* * *

I often see the definition:

`f _|_ = _|_ <=> f strict`

In purely theoretical terms, I think this definition makes sense.

But in Haskell discussions, strictness seems to mean more than just this.  
For example, if we only care about the above definition, the following would be strict:

```f
f !x = g x

f undefined -- undefined
f (1 + undefined) -- undefined 

```

[Aside: actually, if I took the definition at face value, then `id undefined = undefined` so `id` would be strict as well. I’m not sure who is responsible (the function itself or the caller) for evaluating the arguments in this definition]

But I think in typical Haskell discussions, this isn’t what is meant by strictness, especially if `g` is lazy and accumulates a lot of thunks.  
Instead we would need at least something like:

```haskell
f' x = let !y = g x in y

```

and only then would we say `f'` is strict. (or even this isn’t enough depending on `g`, such as with `foldl'`)

But we may also differentiate the two and say `f` is _strict in its argument_ and `f'` is _strict in its result_, or something along those lines.  
e.g.

> **[All about strictness](https://academy.fpblock.com/haskell/tutorial/all-about-strictness/)**

> **[6.14. Bang patterns and Strict Haskell — Glasgow Haskell Compiler 9.2.8...](https://downloads.haskell.org/~ghc/9.2.8/docs/html/users_guide/exts/strict.html)**

* * *

Either way, it’s a bit different from the definition `f _|_ = _|_`.  
Put another way, in Haskell discussions, strictness isn’t so much about the handling of bottom, but more about thunk accumulation and evaluation, which I believe are related but not exactly the same thing. e.g. `foldl'`

But there are also cases where strictness does refer to the original definition, like [Data.Function](https://hackage-content.haskell.org/package/base/docs/Data-Function.html#v:fix)

And I find myself easily confused when I start thinking about related topic like evaluation and WHNF, the `evaluate` function, packages with lazy and strict versions, memory leaks such as those in `Control.Monad.Trans.Writer.Strict` vs `Control.Monad.Trans.Writer.CPS` (not entirely due to strictness, but still part of the problem), etc

I was wondering if there is a more precise definition of these different strictnesses in Haskell, and whether there is some comprehensive resource on this topic (I’ve looked so far in common places like the language report, GHC docs, Haskell wiki).  
Thank you!

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [June 28, 2026, 8:22am UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/2 "2026-06-28T08:22:07Z")

</div>

> [@ynishiza](#):
>
> But I think in typical Haskell discussions, this isn’t what is meant by strictness, especially if `g` is lazy and accumulates a lot of thunks.  
> Instead we would need at least something like:
> 
> `f' x = let !y = g x in y`

This is lazy and the same as `f x = g x`. There is no such thing as “strict in the result” (or alternatively: every function is strict in its result).

There is some nuance to the `f ⊥ = ⊥` definition, which is that the laziness might be deeper in the argument if it is a data structure. For example, `f (x, y) = g x y` is technically strict, but in some sense it is still lazy in the `x` and `y` components of the tuple (assuming `g` is lazy in its arguments).

Higher-order functions complicate this even further. When a function can be given an arbitrary function as argent, GHC must assume that the argument function is lazy. Consider the naive `foldl`:

```haskell
foldl k z [] = z
foldl k z (x:xs) = foldl k (k z x) xs

```

Here the first case is strict in `z`, but in the second case `z` is only used as an argument to `k` and that can be any function, so we must assume it is lazy. Note that this doesn’t mean that we need to allocate a thunk for `z`, but it does mean that we need to allocate a thunk to store the unevaluated `k z x` function which we later pass back into the `z` parameter of the next iteration of the `foldl`.

~~If you define foldl so that `foldl k ⊥ xs = ⊥`, for example by adding a bang pattern on `z`, then you really do have `foldl'`. That is basically the only difference.~~ It is possible to define a `foldl'` with strict semantics that still leaks memory.

---

<div class="post-metadata">

**Author:** ![mpilgrem](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/mpilgrem/32/2529_2.png) [@mpilgrem](https://discourse.haskell.org/u/mpilgrem)\
**Post date:** [June 28, 2026, 12:44pm UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/3 "2026-06-28T12:44:10Z")

</div>

There is a series of four videos here on Laziness in Haskell, which (if you have not seen them) may interest you:

- [https://youtu.be/fSqE-HSh\_NU?si=IGmkMeP-U50gvEkk](https://youtu.be/fSqE-HSh_NU?si=IGmkMeP-U50gvEkk)
- [https://youtu.be/NCM8pRiLtAc?si=AA7jRImKpJgNfFZI](https://youtu.be/NCM8pRiLtAc?si=AA7jRImKpJgNfFZI)
- [https://youtu.be/Xceng7i98Y0?si=Wq0RfcaCQVy4TxEb](https://youtu.be/Xceng7i98Y0?si=Wq0RfcaCQVy4TxEb)
- [https://youtu.be/wC9cpQk7WWA?si=z8efmV57POB9EoU2](https://youtu.be/wC9cpQk7WWA?si=z8efmV57POB9EoU2)

---

<div class="post-metadata">

**Author:** ![ynishiza](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ynishiza/32/4707_2.png) [@ynishiza](https://discourse.haskell.org/u/ynishiza)\
**Post date:** [June 29, 2026, 10:17am UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/4 "2026-06-29T10:17:17Z")

</div>

Thank you, @jaror !  
I think I realized where my whole misunderstanding came from and feel quite embarrassed about the question now.  
I’d committed the common mistake of conflating **non-strict semantics** with **laziness** , and confused myself with it.  
In particular, I kept thinking about thunks when considering strictness (hence where the thought of strict in result came from), but that’s not really relevant at least as far as strictness is concerned, since it’s about denotational semantics and not evaluation…

To confirm my understanding:

> [@jaror](#):
>
> For example, `f (x, y) = g x y` is technically strict, but in some sense it is still lazy in the `x` and `y` components of the tuple (assuming `g` is lazy in its arguments).

we could make this strict by something like `f (!x, !y) = g x y` . So then it would be strict in both the pair and its values, regardless of the strictness of `g`.

I think I ended up confusing myself because performance discussions often mention making things “strict” or “stricter”. This is because strict functions typically perform better, but to be precise, strictness itself isn’t about performance (since it’s about denotational semantics).  
Is this correct..?

---

<div class="post-metadata">

**Author:** ![ynishiza](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ynishiza/32/4707_2.png) [@ynishiza](https://discourse.haskell.org/u/ynishiza)\
**Post date:** [June 29, 2026, 10:20am UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/5 "2026-06-29T10:20:50Z")

</div>

@mpilgrem I recall watching bits of it a couple of years ago, but this would be a good opportunity to revisit. Thanks!

---

<div class="post-metadata">

**Author:** ![olf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/olf/32/3813_2.png) [@olf](https://discourse.haskell.org/u/olf)\
**Post date:** [June 29, 2026, 8:27pm UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/6 "2026-06-29T20:27:52Z")

</div>

> [@ynishiza](#):
>
> In particular, I kept thinking about thunks when considering strictness

One must not confuse operational and denotational semantics. Experts like @sgraf may correct me, but a _thunk_ is operational: A memory representation of a not-fully-evaluated piece of code. Even in a fully strict language, the run-time may choose to work with intermediate thunks. In contrast, bottom ⊥ is denotational.

The various strictness annotations in Haskell or GHC also have both operational and denotational consequences. When defining

```haskell
data Foo = Foo {
   getA :: !A
   }

```

it means that the denotation of type `Foo` does not contain more elements than the denotation of type `A`. But it also means that pattern matching a `Foo` will operationally force any thunk that resides in the `A`. (Without the bang, the elements `Foo ⊥` and `⊥` are distinct.)

Do you know the `:print` command in GHCi? It can be used to explore how strict a function is.

```haskell
Prelude> nats = 0 : map succ nats :: [Integer]
Prelude> :print nats
nats = (_t1::[Integer])

```

This shows that currently, `nats` is basically a thunk, or just as good as ⊥.  
Now let’s evaluate it a little:

```haskell
Prelude> take 2 nats
[0,1]
Prelude> :print nats
nats = 0 : 1 : (_t2::[Integer])

```

The runtime now knows that nats is _at least_ `0 : 1 : ⊥` but we have not looked further. Conclusion: `take 2` is strict in the sense that `take 2 undefined = undefined`. But it is also lazy because it fails to distinguish `0 : 1 : undefined` from e.g. `0 : 1 : 2 : []`.

---

<div class="post-metadata">

**Author:** ![augustss](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/augustss/32/887_2.png) [@augustss](https://discourse.haskell.org/u/augustss)\
**Post date:** [June 29, 2026, 8:54pm UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/7 "2026-06-29T20:54:46Z")

</div>

Semantic strictness is more than just about evaluation. Take the tail recursive factorial function

`fact :: Int -> Int -> Int`  
`fact r 0 = r`  
`fact r n = fact (n*r) (n-1)`  
`fac :: Int -> Int`  
`fac = fact 1`

Is `fact` strict in its first argument? It’s subtle. If we call `fac` with a negative number then `fact` will never reach the base case, so `r` will never get evaluated. So one might think that we cannot conclude that `fact` is strict in its first argument, but in fact it is! If we look at `fact _|_ n`, what can happen? If `n>=0` we reach the base case and the result is `_|_`, if `n<0` then `fact` will not terminate, which is also `_|_`. So no matter what, passing `_|_` as the first argument will result in `_|_`, so `fact` is strict in its first argument. A strictness analyzer can figure this out (quite easily, in fact) and so the `n*r` multiplication can be perforrmed before doing the tail call. And so `fact` runs nicely in constant space.

---

<div class="post-metadata">

**Author:** ![ynishiza](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ynishiza/32/4707_2.png) [@ynishiza](https://discourse.haskell.org/u/ynishiza)\
**Post date:** [June 29, 2026, 9:17pm UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/8 "2026-06-29T21:17:35Z")

</div>

@olf Thank you. I have been aware of these as features, but it all makes more sense now.

---

<div class="post-metadata">

**Author:** ![ynishiza](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ynishiza/32/4707_2.png) [@ynishiza](https://discourse.haskell.org/u/ynishiza)\
**Post date:** [June 29, 2026, 9:28pm UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/9 "2026-06-29T21:28:03Z")

</div>

@augustss Thank you. So, in ghc this would be `-fstrictness`?

> [@augustss](#):
>
> A strictness analyzer can figure this out

update: this indeed is a curious example. Taking a second look..

---

<div class="post-metadata">

**Author:** ![sgraf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sgraf/32/392_2.png) [@sgraf](https://discourse.haskell.org/u/sgraf)\
**Post date:** [June 29, 2026, 10:16pm UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/10 "2026-06-29T22:16:38Z")

</div>

Indeed, “`f` is strict” is a neat mathy notion from topology, apparently remote from “computing”, yet it supposedly has actual operational content: any non-constant program `f : Int -> Int` that reliably diverges/errors when its argument does _must_ evaluate that argument on every code path (at least once); otherwise it solves the halting problem.

That is the property we exploit in Haskell. Equational reasoning gives us a nice way to reason about a function’s strictness, and hence a way to reason about its operational behavior too! Furthermore, we can justify switching from call-by-need to call-by-value (an operational flavour) by trusting the results of a _strictness_ analysis (a denotational flavour). The latter is justified by a denotational semantics, not by an operational one.

In fact, there’s a subtlety. Strictness analysis is not _actually_ a safe “evaluated at least once” analysis. The function `\x y -> x `seq` y` is strict in both `x` and `y`, but if `x` diverges we never evaluate `y`! So “strictness = evaluated at least once” is wrong if we actually start caring about diverging programs, or just the distinction between crashing and diverging (or crashing differently).

Another way to put it: strictness isn’t a [_safety property_](https://en.wikipedia.org/wiki/Safety_and_liveness_properties). Being strict in `y` is supposed to guarantee that `y` gets evaluated, and a safety property would let us point to the exact step/trace prefix where that happens. But on the diverging `x `seq` y` trace there is no such step: we get stuck evaluating `x`, never reach `y`, yet `f` is still strict in `y`. The denotational semantics is blind to this, because it collapses every divergence to the same `_|_`. IMO that’s one of the big gaping holes in traditional (non-trace-based) denotational semantics.

I think Lennart’s example points at the exact same problem at the intuitive level.

This subtlety has resurfaced in GHC a number of times, and it’s practically unfixable without incurring regressions in production code.

---

<div class="post-metadata">

**Author:** ![ynishiza](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ynishiza/32/4707_2.png) [@ynishiza](https://discourse.haskell.org/u/ynishiza)\
**Post date:** [July 1, 2026, 6:36am UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/11 "2026-07-01T06:36:12Z")

</div>

It’ll take me a while to digest this but this is very fascinating.  
I am tempted to explore further, though I fear it’ll be a very deep rabbit hole…

Nevertheless, I started reading a blog post which I believe is yours from a few years ago on strictness analysis: [fixpt · All About Strictness Analysis (part 1)](https://fixpt.de/blog/2017-12-04-strictness-analysis-part-1.html)

I tried `ghc -O2 strict.hs -ddump-stranal -fforce-recomp ` on @augustss 's factorial:

```haskell
$wfact_s2be [InlPrag=[2], Occ=LoopBreaker, Dmd=LC(L,C(1,L))]
  :: GHC.Prim.Int# -> GHC.Prim.Int# -> GHC.Prim.Int#
[LclId[StrictWorker([])],
 Arity=2,
 Str=<L><1L>,
 Unf=Unf{Src=<vanilla>, TopLvl=True,
         Value=True, ConLike=True, WorkFree=True, Expandable=True,
         Guidance=IF_ARGS [0 30] 52 0}]
$wfact_s2be
  = \ (ww_s2b6 :: GHC.Prim.Int#)
      (ww_s2ba [Dmd=1L] :: GHC.Prim.Int#) ->
      case ww_s2ba of ds_X1 {
        __DEFAULT ->
          $wfact_s2be (GHC.Prim.*# ds_X1 ww_s2b6) (GHC.Prim.-# ds_X1 1#);
        0# -> ww_s2b6
      }

```

which I believe shows that it did infer strictness (and calls by value unboxed) in both arguments. And profiling `fac 1000000000`:

```haskell
50,440 bytes allocated in the heap
           3,272 bytes copied during GC
          44,328 bytes maximum residency (1 sample(s))
          25,304 bytes maximum slop
               6 MiB total memory in use (0 MiB lost due to fragmentation)

...

  INIT time 0.001s ( 0.001s elapsed)
  MUT time 0.943s ( 0.944s elapsed)
  GC time 0.000s ( 0.000s elapsed)
  EXIT time 0.000s ( 0.011s elapsed)
  Total time 0.944s ( 0.957s elapsed)

```

Just to play around, I tried the same with explicit strictness and the result was the same

```haskell
fact' !r 0 = r
fact' !r n = fact' (n * r) (n - 1)

```

* * *

I apologize that this may be going off on a tangent, but I have one question from the example in the blog post.`printListAverage` and `fact` here seem similar in that both have an argument that may never get evaluated, and yet the opposite seems to be happening.

In the `fact` case, strictness analysis does determine that the function is strict in `r` despite for a negative `n` like `fact undefined -1`, the crash is not due to `r` but non-termination.

But for `printListAverage`, strictness analysis doesn’t make `sum` call-by-value, and this is attributed to the `error` call not being the same as `undefined`

> `printAverage (RunningTotal undefined 0)` will print the expected error message instead of crashing due to `undefined`, which is the very definition of being lazy in `sum`. This extends to a call like `go (RunningTotal undefined 0) []`, so GHC can’t just unbox the `sum` field even if the recursive case of `go` is annotated.

Put another way, it seems like in `fact`’s case, the differences in ways of crashing/diverging doesn’t matter, whereas in `printListAverage` it does?  
What am I missing..?

I’m using `ghc 9.6.7`.

p.s.  
Changing the `| count == 0 = error "..."` to `| count == 0 = undefined` didn’t make a difference either… On the otherhand, putting the strictness on `x` in the list alone instead of `printAverage (RunningTotal !sum count)` appears to perform equally well, if I’m not mistaken.

```haskell
  go rt [] = printAverage rt
  go (RunningTotal sum count) (!x : xs) = -- the only !
    go (RunningTotal (sum + x) (count + 1)) xs

```

---

<div class="post-metadata">

**Author:** ![sgraf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sgraf/32/392_2.png) [@sgraf](https://discourse.haskell.org/u/sgraf)\
**Post date:** [July 1, 2026, 9:08pm UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/12 "2026-07-01T21:08:33Z")

</div>

> [@ynishiza](#):
>
> Put another way, it seems like in `fact`’s case, the differences in ways of crashing/diverging doesn’t matter, whereas in `printListAverage` it does?

A sharp realization 🙂 I think the reason is that `print` is lazy in its argument. Note that `print` does quite a lot of `IO` related things.

---

<div class="post-metadata">

**Author:** ![jotaalvim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jotaalvim/32/4691_2.png) [@jotaalvim](https://discourse.haskell.org/u/jotaalvim)\
**Post date:** [July 2, 2026, 3:20pm UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/13 "2026-07-02T15:20:41Z")

</div>

I’m sure people above have explained it well enough, but let me add one more interesting reference:

I remembered watching these 5 min of SPJ explaining the relationship between laziness and strictness by comparing Haskell to OCaml.

[https://www.youtube.com/watch?v=xcB\_LF3cdqw&list=WL&index=42&t=3202s](https://youtu.be/xcB_LF3cdqw?si=hdCf2xiaqJpuGwPU&t=1711)

---

<div class="post-metadata">

**Author:** ![ynishiza](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ynishiza/32/4707_2.png) [@ynishiza](https://discourse.haskell.org/u/ynishiza)\
**Post date:** [July 3, 2026, 7:51am UTC](https://discourse.haskell.org/t/meaning-of-strictness/14326/14 "2026-07-03T07:51:18Z")

</div>

Ah right, it goes through print and the result is `IO ()`!
