# Why not use smallcheck?

**URL:** <https://discourse.haskell.org/t/why-not-use-smallcheck/13860>\
**Category:** Uncategorized\
**Created:** [March 30, 2026, 6:46pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860 "2026-03-30T18:46:05Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [March 30, 2026, 6:46pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/1 "2026-03-30T18:46:05Z")

</div>

On the [smallcheck package](https://hackage.haskell.org/package/smallcheck) there is a disclaimer:

> **As of 2023, this library is largely obsolete: arbitrary test generators with shrinking such as [`falsify`](https://hackage.haskell.org/package/falsify) offer much better user experience.**

I don’t understand why that is. As great `falsify` is, I find it annoying to write generators again and again. Maybe I’m not using generics and deriving via enough, I find it a bit hard to wrap my head around in that library.

But I find the principles of smallcheck so great: By construction, the smallest test case will be tested first. No worries about shrinking. Series of test values can be implemented without boilerplate, with generics.

So why would someone claim that smallcheck has worse user experience? What’s wrong with it?

---

<div class="post-metadata">

**Author:** ![Bodigrim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bodigrim/32/1457_2.png) [@Bodigrim](https://discourse.haskell.org/u/Bodigrim)\
**Post date:** [March 30, 2026, 7:10pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/2 "2026-03-30T19:10:55Z")

</div>

Exponential explosion even in simple cases (like, a record with 6-7 fields) makes `smallcheck` very brittle to control and use in anger.

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [March 30, 2026, 7:26pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/3 "2026-03-30T19:26:28Z")

</div>

I’m surprised this happens so quickly. Could it be that this is implementation specific because of `MonadLogic`? What if `Series` would be implemented as a simple list that contains all the values in ascending size? After all, we don’t really need backtracking, so `MonadLogic` is maybe oversized.

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [March 30, 2026, 8:26pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/4 "2026-03-30T20:26:50Z")

</div>

I made a little POC implementation of a smallcheck-like library here: [tinycheck/src/Data/TestCases.hs at main · turion/tinycheck · GitHub](https://github.com/turion/tinycheck/blob/main/src/Data/TestCases.hs)

It only needs `base` as a dependency and works with lists. It runs 10 million tests on a largeish record (8 fields, some of them recursive) in less then half a minute on my laptop.

Can you provide me with a test that explodes in `smallcheck`? I’ll have a look whether it’s viable in this testing framework.

---

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [March 30, 2026, 9:44pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/5 "2026-03-30T21:44:55Z")

</div>

Feels like it doing what it says on the tin

Exhaustive checking is pretty underused imo. “only X many floats just test them all” is p effective.

---

<div class="post-metadata">

**Author:** ![Bodigrim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bodigrim/32/1457_2.png) [@Bodigrim](https://discourse.haskell.org/u/Bodigrim)\
**Post date:** [March 30, 2026, 10:38pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/6 "2026-03-30T22:38:22Z")

</div>

@turion the problem is not the raw performance of inputs generation, the problem is that for a given depth their number grows exponentially. So with default depth 5 a record with 6 fields will generate roughly 5^6 test cases. And if your property takes two of such records, it’s 5^12, almost a quarter of a billion of them. So at this point you start to micromanage `smallcheck` tweaking depth here and there (which is already annoying) and end up setting depth of 2 for such test. It’s still at least 2^12 test cases, which is probably okayish, but you are barely testing anything with such depth, right?..

Imagine testing a property on strings. What would you like `smallcheck` to do? Should it limit itself to enumerating `['a'..]` for small depth? In such case it would never generate even something very simple, like a string with a space. Or a digit. Or an upper case letter. `quickcheck` is so much better: in a matter of microseconds it will throw all kinds of inputs on your test property.

---

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [March 30, 2026, 11:19pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/7 "2026-03-30T23:19:12Z")

</div>

strings don’t see like a reason to deprecate smallcheck in favor of QC tho lolol. A reason to use QC, yeah. But using sc with strings is just using it wrong..

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [March 31, 2026, 4:16pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/8 "2026-03-31T16:16:24Z")

</div>

I guess I understand to little how depth works in `smallcheck`. In my `tinycheck`, you could simply define something like:

```haskell
string :: TestCases String
string = arbitrary
  <> (unwords <$> arbitrary)
  <> (unlines . fmap unwords <$> arbitrary)

```

and you’d get strings, strings separated by spaces, strings separated by spaces and newlines both very early on. If it’s important to have spaces and newlines, then you can simply merge a generator that produces them into the default one.

Or if you need longer strings, you can do:

```haskell
atLeast :: Int -> TestCases String
atLeast n = (<>) <$> replicateM n arbitrary <*> arbitrary

```

My point is, if you have requirements of some specific data showing up in the generator, then fix the generator. But I don’t see how there is a fundamental limitation to enumerating values.

Ok, here is maybe one thing: The concept of depth is maybe too restrictive. My approach here is to just enumerate in some order ad hoc, without keeping track precisely how deep we are going. If we need a few deeper values early on, just mix them into the generator. Keeping track of depth is not necessary, we can just restrict the number of test cases instead.

---

<div class="post-metadata">

**Author:** ![Bodigrim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bodigrim/32/1457_2.png) [@Bodigrim](https://discourse.haskell.org/u/Bodigrim)\
**Post date:** [March 31, 2026, 6:19pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/9 "2026-03-31T18:19:43Z")

</div>

> [@turion](#):
>
> If it’s important to have spaces and newlines, then you can simply merge a generator that produces them into the default one.

How would you know upfront whether it’s important or not? If I definitely know that something is important, I’ll write a unit test to nail it down, no need for quickcheck or smallcheck. The very point of property testing is to get confronted by unknown. And QuickCheck will just throw at me spaces, newlines, digits, punctuation, upper case, lower case, Unicode and what not. While SmallCheck will require me writing a dozen of newtypes with different generators and tweak depth here and there. And all of it for what?

I mean, one can definitely make SmallCheck work. I have fairly [extensive test suites](https://hackage.haskell.org/package/arithmoi) using SmallCheck. Guess what? I’ve never seen a case where it would spot a bug, which QuickCheck didn’t.

> [@Ambrose](#):
>
> strings don’t see like a reason to deprecate smallcheck in favor of QC tho lolol. A reason to use QC, yeah. But using sc with strings is just using it wrong..

What would be a reason to use SmallCheck _tho lolol_?

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [March 31, 2026, 6:51pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/10 "2026-03-31T18:51:13Z")

</div>

> [@Bodigrim](#):
>
> What would be a reason to use SmallCheck _tho lolol_?

Not wanting to write a shrinker _lolol_ 🤣 but right, if the downside is that I have to put more effort into the generator, I can see the point.

---

<div class="post-metadata">

**Author:** ![Leary](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/leary/32/4348_2.png) [@Leary](https://discourse.haskell.org/u/Leary)\
**Post date:** [March 31, 2026, 7:15pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/11 "2026-03-31T19:15:20Z")

</div>

> [@Bodigrim](#):
>
> Guess what? I’ve never seen a case where it would spot a bug, which QuickCheck didn’t.

Not surprising; that’s really not where the value of exhaustive—or rather, _simplest first_—testing lies. IMO the big win is getting a genuinely minimal counterexample to your property in cases where greedy shrinking just isn’t good enough.

---

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [March 31, 2026, 7:17pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/12 "2026-03-31T19:17:05Z")

</div>

> [@Bodigrim](#):
>
> What would be a reason to use SmallCheck _tho lolol_?

When my domain is `small` enough? I’d expect most professional developers to be able to make that determination..

QC “works” sometimes but also for certain types of programs, that kind of random testing is an easy way to get master red and annoy everyone you work with randomly.

idk I just see “deprecated in favor of” as meaning the two things are actually equivalent fundamentally. not opinion-wise.

---

<div class="post-metadata">

**Author:** ![Bodigrim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bodigrim/32/1457_2.png) [@Bodigrim](https://discourse.haskell.org/u/Bodigrim)\
**Post date:** [March 31, 2026, 7:47pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/13 "2026-03-31T19:47:24Z")

</div>

> [@turion](#):
>
> Not wanting to write a shrinker _lolol_ 🤣 but right, if the downside is that I have to put more effort into the generator, I can see the point.

Realistically `shrink` is already implemented for all basic types and then `genericShrink` gets you 90% there. There are use cases for writing `shrink` manually, but they are usually in areas which are far beyond SmallCheck’s reach anyway.

> [@Leary](#):
>
> IMO the big win is getting a genuinely minimal counterexample to your property in cases where greedy shrinking just isn’t good enough.

Could you give an example?

> [@Ambrose](#):
>
> idk I just see “deprecated in favor of” as meaning the two things are actually equivalent fundamentally. not opinion-wise.

Perhaps you are seeing something which is not there?.. SmallCheck description does not contain your quote.

---

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [March 31, 2026, 8:50pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/14 "2026-03-31T20:50:12Z")

</div>

oh whoops - wrong word, same idea (“obsolete”)

---

<div class="post-metadata">

**Author:** ![Leary](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/leary/32/4348_2.png) [@Leary](https://discourse.haskell.org/u/Leary)\
**Post date:** [April 1, 2026, 3:04am UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/15 "2026-04-01T03:04:54Z")

</div>

Greedy shrinking will naturally fail to shrink any part of a counterexample that must relate to some other part of the counterexample. In the common case it can still shrink around one or two simple interrelations (e.g. two numbers that must be equal) and produce something simple enough. When those interrelations become more complex and plentiful, however, a greedy shrinker stalls out all too soon.

In particular, I’ve seen this when shrinking things that are more code-like than data-like, e.g. a representation of an opaque type by an AST of its operations.

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [April 1, 2026, 8:40am UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/17 "2026-04-01T08:40:43Z")

</div>

To counter the previous post, let me just say briefly that I thank you for your maintenance to the library and your detailed responses here. I fully respect your decision to invest your time into projects that you find more rewarding.

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [April 2, 2026, 6:18pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/18 "2026-04-02T18:18:40Z")

</div>

I’d still be interested in a very concrete test case that seems infeasible with smallcheck and is easy with QuickCheck

---

<div class="post-metadata">

**Author:** ![Bodigrim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bodigrim/32/1457_2.png) [@Bodigrim](https://discourse.haskell.org/u/Bodigrim)\
**Post date:** [April 2, 2026, 11:26pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/19 "2026-04-02T23:26:40Z")

</div>

[The premise behind SmallCheck](https://hackage.haskell.org/package/smallcheck-1.2.1.1#readme), namely, that

> If a program fails to meet its specification in some cases, it almost always fails in some simple case.

is false. There are many practical cases where a minimal counterexample is not that small or simple. [Hybrid algorithms](https://en.wikipedia.org/wiki/Hybrid_algorithm) provide many examples.

To be concrete, imagine testing a hybrid sorting algorithm, which starts with divide-and-conquer quicksort, but switches to insertion sort once subarrays are less then N elements. On modern hardware typical values of N are `[15..20]` (say, it’s [18](https://hackage-content.haskell.org/package/vector-algorithms-0.9.1.0/docs/src/Data.Vector.Algorithms.Intro.html#threshold) in `vector-algorithms`). If you try to test it with SmallCheck you would never ever get into testing quicksort part of it, which could happily remain `undefined`, because enumerating all arrays up to 18 elements will take too long. But QuickCheck will get there fairly quickly.

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [April 7, 2026, 2:22pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/20 "2026-04-07T14:22:59Z")

</div>

I see your point, but I still find that this is a too narrow view on how to treat depth. Here, you implicitly identify depth with length of the array. This would lead to an unusable implementation. For example, `[Integer]` would enumerate all (i.e. infinitely many) one-element lists before the 2-element lists. Clearly this is not what we want. It’s not what `tinycheck` implements, and I assumed until now it’s not what `smallcheck` implements?

For your specific example, for a threshold of 18, `tinycheck` finds the bug after less than 2 million (and more than 200 000) values, which takes 0.3 seconds on my machine. (1.4 seconds for a threshold of 20, still less than 2 million values.) I agree that this is probably less performant than QuickCheck, but still I find it noteable. I achieved it by just writing down the basic idea behind SmallCheck from memory in little time as a library. This says something about the power of the idea.

---

<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:** [April 7, 2026, 3:16pm UTC](https://discourse.haskell.org/t/why-not-use-smallcheck/13860/21 "2026-04-07T15:16:08Z")

</div>

> [@turion](#):
>
> For your specific example, for a threshold of 18, `tinycheck` finds the bug after less than 2 million (and more than 200 000) values, which takes 0.3 seconds on my machine.

Hah, I wanted to write a comment here about how the enumeration could order the candidates based on the sum of all the integers in the list (e.g. `[1,1]` would come immediately after or before `[2]`). But then you’re still generating an exponential amount of tests. It turns out that 18 is still kind of reachable at 131.072 tests before you reach `replicate 18 1`. However, it takes _many_ more tests before you get to any kind of representative array of 18 numbers. With every value of the sum you add, the number of tests required doubles. I’m guessing you were lucky that the bug appeared for an array with 17 zeroes and a single one (or something like that), which would “only” require between 262.144 and 524.288 tests.

* * *

Thinking about it again, maybe you could do much better if you made and ordering based on merging different orders of magnitude. Consider generating tests for a single integer. We could consider the ranges 1-10, 10-100, 100-1000, etc. separately and then merge their orders. So you’d get 0, 10, 100, …, 1, 11, 101, …, 2, 12, 102, …  
For lists (or vectors) you’d first test length 1, then length 10, then 100, etc.

I guess one problem will be that the larger lists will take much longer to tests, so maybe we want to bias this more to the smaller lists. Also, this means we would probably want shrinking again.

[Next page](https://discourse.haskell.org/t/why-not-use-smallcheck/13860.md?page=2)
