Simply put, a value of type Int can be an invaluable expression. The actual value is not calculated until you "look" at the value.
A value of type Int# is an evaluated result. Is always.
As a result of this, a Int a data structure that lives on the heap. Int# is just a 32 bit integer. It can work in the CPU register. You can work with it with one machine instruction. This is almost no overhead.
In contrast, when you write, say, x + 1 , you do not actually calculate x + 1, you create a data structure on the heap that says "when you want to calculate this, do x + 1".
Simply put, Int# faster because it cannot be lazy.
When do you use it? Never again. This is the work of the compiler. The idea is that you write good, high-level Haskell code involving Int , and the compiler determines where it can replace Int with Int# . (We hope!) If this is not the case, it is almost always easier to insert a few annotations of stringency rather than playing directly with Int# . (It is also not portable, only GHC uses Int# - although there are currently no other widely used Haskell compilers.)
source share