diff --git a/demo/Ints/Main.hs b/demo/Ints/Main.hs
new file mode 100644
--- /dev/null
+++ b/demo/Ints/Main.hs
@@ -0,0 +1,71 @@
+module Main where
+
+import           System.Random                 (RandomGen, mkStdGen, randomR)
+
+import           Data.IntSet                   (IntSet)
+import qualified Data.IntSet                   as IntSet
+import qualified Data.PerfectHash.Construction as Construction
+import qualified Data.PerfectHash.Hashing      as Hashing
+import qualified Data.PerfectHash.Lookup       as Lookup
+import qualified Data.Vector.Unboxed           as Vector
+import           Exercise                      (Atom (Atom))
+import qualified Exercise
+
+
+valueCount = 500000
+
+randomRange = (0, Hashing.mask32bits)
+
+
+data RandIntAccum t = RandIntAccum
+  t -- ^ random number generator
+  Int -- ^ max count
+  IntSet -- ^ accumulated unique random numbers
+
+
+-- | Since computing the size of the set is O(N), we
+-- maintain the count separately.
+getUniqueRandomIntegers :: RandomGen t => RandIntAccum t -> IntSet
+getUniqueRandomIntegers (RandIntAccum std_gen count current_set) =
+
+  if count == 0
+    then current_set
+    else getUniqueRandomIntegers newstate
+
+  where
+    (next_int, next_std_gen) = randomR randomRange std_gen
+
+    a = RandIntAccum next_std_gen
+    newstate = if IntSet.member next_int current_set
+      then a count current_set
+      else a (count - 1) (IntSet.insert next_int current_set)
+
+
+intMapTuples :: [(Atom Int, Int)]
+intMapTuples = zip (map Atom random_ints) [1..]
+  where
+    seed_value = RandIntAccum (mkStdGen 0) valueCount IntSet.empty
+    random_ints = IntSet.toList $ getUniqueRandomIntegers seed_value
+
+
+main = do
+
+  putStrLn $ unwords ["Keys size:", show $ length intMapTuples]
+
+  let lookup_table = Construction.createMinimalPerfectHash intMapTuples
+
+  putStrLn $ unwords [
+      "Finished computing lookup table with"
+    , show $ Lookup.size lookup_table
+    , "entries."
+    ]
+
+  let direct_mapping_nonces = Vector.filter (< 0) $ Lookup.nonces lookup_table
+
+  putStrLn $ unwords [
+      "There were"
+    , show $ Vector.length direct_mapping_nonces
+    , "lookup entries with direct mappings."
+    ]
+
+  Exercise.eitherExit $ Exercise.testLookups lookup_table intMapTuples
diff --git a/demo/IntsDemo.hs b/demo/IntsDemo.hs
deleted file mode 100644
--- a/demo/IntsDemo.hs
+++ /dev/null
@@ -1,71 +0,0 @@
-module Main where
-
-import           System.Random                 (RandomGen, mkStdGen, randomR)
-
-import           Data.IntSet                   (IntSet)
-import qualified Data.IntSet                   as IntSet
-import qualified Data.PerfectHash.Construction as Construction
-import qualified Data.PerfectHash.Hashing      as Hashing
-import qualified Data.PerfectHash.Lookup       as Lookup
-import qualified Data.Vector.Unboxed           as Vector
-import           Exercise                      (Atom (Atom))
-import qualified Exercise
-
-
-valueCount = 500000
-
-randomRange = (0, Hashing.mask32bits)
-
-
-data RandIntAccum t = RandIntAccum
-  t -- ^ random number generator
-  Int -- ^ max count
-  IntSet -- ^ accumulated unique random numbers
-
-
--- | Since computing the size of the set is O(N), we
--- maintain the count separately.
-getUniqueRandomIntegers :: RandomGen t => RandIntAccum t -> IntSet
-getUniqueRandomIntegers (RandIntAccum std_gen count current_set) =
-
-  if count == 0
-    then current_set
-    else getUniqueRandomIntegers newstate
-
-  where
-    (next_int, next_std_gen) = randomR randomRange std_gen
-
-    a = RandIntAccum next_std_gen
-    newstate = if IntSet.member next_int current_set
-      then a count current_set
-      else a (count - 1) (IntSet.insert next_int current_set)
-
-
-intMapTuples :: [(Atom Int, Int)]
-intMapTuples = zip (map Atom random_ints) [1..]
-  where
-    seed_value = RandIntAccum (mkStdGen 0) valueCount IntSet.empty
-    random_ints = IntSet.toList $ getUniqueRandomIntegers seed_value
-
-
-main = do
-
-  putStrLn $ unwords ["Keys size:", show $ length intMapTuples]
-
-  let lookup_table = Construction.createMinimalPerfectHash intMapTuples
-
-  putStrLn $ unwords [
-      "Finished computing lookup table with"
-    , show $ Lookup.size lookup_table
-    , "entries."
-    ]
-
-  let direct_mapping_nonces = Vector.filter (< 0) $ Lookup.nonces lookup_table
-
-  putStrLn $ unwords [
-      "There were"
-    , show $ Vector.length direct_mapping_nonces
-    , "lookup entries with direct mappings."
-    ]
-
-  Exercise.eitherExit $ Exercise.testLookups lookup_table intMapTuples
diff --git a/demo/Strings/Main.hs b/demo/Strings/Main.hs
new file mode 100644
--- /dev/null
+++ b/demo/Strings/Main.hs
@@ -0,0 +1,33 @@
+module Main where
+
+import           Control.Monad                 (when)
+
+import qualified Data.PerfectHash.Construction as Construction
+import qualified Data.PerfectHash.Lookup       as Lookup
+import qualified Exercise
+
+
+enableDebug = False
+
+dictionaryPath = "/usr/share/dict/words"
+
+
+main = do
+
+  word_index_tuples <- Exercise.wordsFromFile dictionaryPath
+
+  putStrLn $ unwords ["Words size:", show $ length word_index_tuples]
+
+  let lookup_table = Construction.createMinimalPerfectHash word_index_tuples
+
+  putStrLn $ unwords [
+      "Finished computing lookup table with"
+    , show $ Lookup.size lookup_table
+    , "entries."
+    ]
+
+  when enableDebug $ do
+    putStrLn $ unwords ["Vector G:", show $ Lookup.nonces lookup_table]
+    putStrLn $ unwords ["Vector V:", show $ Lookup.values lookup_table]
+
+  Exercise.eitherExit $ Exercise.testLookups lookup_table word_index_tuples
diff --git a/demo/StringsDemo.hs b/demo/StringsDemo.hs
deleted file mode 100644
--- a/demo/StringsDemo.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-module Main where
-
-import           Control.Monad                 (when)
-
-import qualified Data.PerfectHash.Construction as Construction
-import qualified Data.PerfectHash.Lookup       as Lookup
-import qualified Exercise
-
-
-enableDebug = False
-
-dictionaryPath = "/usr/share/dict/words"
-
-
-main = do
-
-  word_index_tuples <- Exercise.wordsFromFile dictionaryPath
-
-  putStrLn $ unwords ["Words size:", show $ length word_index_tuples]
-
-  let lookup_table = Construction.createMinimalPerfectHash word_index_tuples
-
-  putStrLn $ unwords [
-      "Finished computing lookup table with"
-    , show $ Lookup.size lookup_table
-    , "entries."
-    ]
-
-  when enableDebug $ do
-    putStrLn $ unwords ["Vector G:", show $ Lookup.nonces lookup_table]
-    putStrLn $ unwords ["Vector V:", show $ Lookup.values lookup_table]
-
-  Exercise.eitherExit $ Exercise.testLookups lookup_table word_index_tuples
diff --git a/perfect-hash-generator.cabal b/perfect-hash-generator.cabal
--- a/perfect-hash-generator.cabal
+++ b/perfect-hash-generator.cabal
@@ -3,7 +3,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           perfect-hash-generator
-version:        0.1.0.2
+version:        0.1.0.3
 synopsis:       Perfect minimal hashing implementation in native Haskell
 description:     A <https://en.wikipedia.org/wiki/Perfect_hash_function perfect hash function> for a set @S@ is a hash function that maps distinct elements in @S@ to a set of integers, with __no collisions__. A <https://en.wikipedia.org/wiki/Perfect_hash_function#Minimal_perfect_hash_function minimal perfect hash function> is a perfect hash function that maps @n@ keys to @n@ __consecutive__ integers, e.g. the numbers from @0@ to @n-1@.
                 .
@@ -71,7 +71,7 @@
   default-language: Haskell2010
 
 executable hash-perfectly-ints-demo
-  main-is: IntsDemo.hs
+  main-is: Ints/Main.hs
   hs-source-dirs:
       demo
       test
@@ -86,13 +86,13 @@
     , hashable
     , containers
   other-modules:
-      StringsDemo
+      Strings.Main
       Exercise
-      Test
+      Main
   default-language: Haskell2010
 
 executable hash-perfectly-strings-demo
-  main-is: StringsDemo.hs
+  main-is: Strings/Main.hs
   hs-source-dirs:
       demo
       test
@@ -106,14 +106,14 @@
     , vector
     , hashable
   other-modules:
-      IntsDemo
+      Ints.Main
       Exercise
-      Test
+      Main
   default-language: Haskell2010
 
 test-suite regression-tests
   type: exitcode-stdio-1.0
-  main-is: Test.hs
+  main-is: Main.hs
   hs-source-dirs:
       test
   ghc-options: -fwarn-tabs -W
diff --git a/src/Data/PerfectHash/Construction.hs b/src/Data/PerfectHash/Construction.hs
--- a/src/Data/PerfectHash/Construction.hs
+++ b/src/Data/PerfectHash/Construction.hs
@@ -54,6 +54,10 @@
     a2 = Vector.generate size (\z -> HashMap.lookupDefault getDefault z $ vals x)
 
 
+-- | Computes a slot in the destination array (Data.PerfectHash.Lookup.values)
+-- for every element in this multi-entry bucket, for the given nonce.
+--
+-- Return a Nothing for a slot if it collides.
 attemptNonceRecursive :: (Foldable f, Hashing.ToNumeric a) =>
      HashMapAndSize Int b
   -> Int
@@ -61,26 +65,29 @@
   -> [f a]
   -> [Maybe Int]
 attemptNonceRecursive _ _ _ [] = []
-attemptNonceRecursive values_and_size nonce previous_slots (x:xs) =
+attemptNonceRecursive values_and_size nonce occupied_slots (x:xs) =
 
   if cannot_use_slot
-    then [Nothing]
+    then pure Nothing
     else Just slot : recursive_result
 
   where
     HashMapAndSize values size = values_and_size
-    slot = Hashing.hash nonce x `mod` size
+    slot = Hashing.hashToSlot nonce x size
 
-    cannot_use_slot = IntSet.member slot previous_slots || HashMap.member slot values
+    cannot_use_slot = IntSet.member slot occupied_slots || HashMap.member slot values
 
-    next_slots = IntSet.insert slot previous_slots
-    recursive_result = attemptNonceRecursive values_and_size nonce next_slots xs
+    recursive_result = attemptNonceRecursive
+      values_and_size
+      nonce
+      (IntSet.insert slot occupied_slots)
+      xs
 
 
 -- | Repeatedly try different values of the nonce until we find a hash function
--- that places all items in the bucket into free slots
+-- that places all items in the bucket into free slots.
 --
--- Keeps trying forever.
+-- Keeps trying forever, incrementing the candidate nonce by @1@ each time.
 -- Theoretically we're guaranteed to eventually find a solution.
 findNonceForBucket :: (Foldable f, Hashing.ToNumeric a) =>
      Int
@@ -99,6 +106,9 @@
       bucket
 
 
+-- | Searches for a nonce for this bucket, starting with the value @1@,
+-- until one is found that results in no collisions for both this bucket
+-- and all previous buckets.
 handleMultiBuckets :: (Foldable f, Hashing.ToNumeric a, Eq (f a), Hashable (f a)) =>
      HashMapAndSize (f a) b
   -> (Int, [f a])
@@ -110,9 +120,9 @@
     HashMapAndSize words_dict size = sized_words_dict
 
     sized_vals_dict = HashMapAndSize (vals old_lookup_table) size
-    (slots, d) = findNonceForBucket 1 sized_vals_dict bucket
+    (slots, nonce) = findNonceForBucket 1 sized_vals_dict bucket
 
-    new_g = HashMap.insert computed_hash d (redirs old_lookup_table)
+    new_g = HashMap.insert computed_hash nonce (redirs old_lookup_table)
     new_values = foldr fold_func (vals old_lookup_table) $ zip [0..] bucket
 
     fold_func (i, bucket_val) = HashMap.insert (slots !! i) $
@@ -150,7 +160,7 @@
   sortOn (negate . length . snd) bucket_hash_tuples
   where
     size = HashMap.size words_dict
-    slot_key_pairs = deriveTuples ((`mod` size) . Hashing.hash 0) $ HashMap.keys words_dict
+    slot_key_pairs = deriveTuples (\k -> Hashing.hashToSlot 0 k size) $ HashMap.keys words_dict
 
     bucket_hash_tuples = HashMap.toList $ binTuplesBySecond slot_key_pairs
 
@@ -192,7 +202,7 @@
 
 -- * Utilities
 
--- | Place the second elements of the tuples into bins according to the second
+-- | Place the first elements of the tuples into bins according to the second
 -- element.
 binTuplesBySecond :: (Eq b, Hashable b) => [(a, b)] -> HashMap.HashMap b [a]
 binTuplesBySecond = foldr f HashMap.empty
diff --git a/src/Data/PerfectHash/Hashing.hs b/src/Data/PerfectHash/Hashing.hs
--- a/src/Data/PerfectHash/Hashing.hs
+++ b/src/Data/PerfectHash/Hashing.hs
@@ -29,6 +29,14 @@
   toNum = id
 
 
+hashToSlot :: (Foldable f, ToNumeric a) =>
+     Int -- ^ nonce
+  -> f a -- ^ key
+  -> Int -- ^ array size
+  -> Int
+hashToSlot nonce key size = hash nonce key `mod` size
+
+
 -- | Uses the \"FNV-1a\" algorithm from the
 -- <http://isthe.com/chongo/tech/comp/fnv/ FNV website>:
 --
diff --git a/src/Data/PerfectHash/Lookup.hs b/src/Data/PerfectHash/Lookup.hs
--- a/src/Data/PerfectHash/Lookup.hs
+++ b/src/Data/PerfectHash/Lookup.hs
@@ -79,10 +79,10 @@
   where
     table_size = size lookup_table
 
-    nonce_index = Hashing.hash 0 key `mod` table_size
+    nonce_index = Hashing.hashToSlot 0 key table_size
     nonce = nonces lookup_table ! nonce_index
 
     -- Negative value indicates that we don't need extra lookup layer
     v_key = if nonce < 0
       then encodeDirectEntry nonce
-      else Hashing.hash nonce key `mod` table_size
+      else Hashing.hashToSlot nonce key table_size
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,59 @@
+module Main where
+
+import           Data.Either                    (isRight)
+import           Data.Hashable                  (Hashable)
+import qualified Data.Vector.Unboxed            as Vector
+import           Test.Framework                 (defaultMain, testGroup)
+import           Test.Framework.Providers.HUnit (testCase)
+import           Test.HUnit                     (assertBool, assertEqual)
+
+import qualified Data.PerfectHash.Construction  as Construction
+import qualified Data.PerfectHash.Hashing       as Hashing
+import           Exercise                       (Atom (Atom))
+import qualified Exercise
+
+
+testHashComputation :: String -> Int -> IO ()
+testHashComputation key val =
+  assertEqual error_message val computed_hash
+  where
+    error_message = unwords ["Incorrect hash computation of", key]
+    computed_hash = Hashing.hash 0 key
+
+
+wordIndexTuples = [
+    ("apple", 1 :: Int)
+  , ("banana", 2)
+  , ("carrot", 3)
+  ]
+
+
+intMapTuples :: [(Atom Int, Int)]
+intMapTuples = [
+    (Atom 1000, 1)
+  , (Atom 5555, 2)
+  , (Atom 9876, 3)
+  ]
+
+
+testHashLookups :: (Show (f a), Show b, Eq b, Vector.Unbox b, Construction.Defaultable b, Foldable f, Hashing.ToNumeric a, Eq (f a), Hashable (f a)) =>
+  [(f a, b)] -> IO ()
+testHashLookups word_index_tuples =
+  assertBool "Perfect hash lookups failed to match the input" $ isRight test_result_either
+  where
+    lookup_table = Construction.createMinimalPerfectHash word_index_tuples
+    test_result_either = Exercise.testLookups lookup_table word_index_tuples
+
+
+tests = [
+    testGroup "Hash computation" [
+      testCase "compute-hash1" $ testHashComputation "blarg" 3322346319
+    ]
+  , testGroup "Hash lookups" [
+      testCase "word-lookups" $ testHashLookups wordIndexTuples
+    , testCase "int-lookups" $ testHashLookups intMapTuples
+    ]
+  ]
+
+
+main = defaultMain tests
diff --git a/test/Test.hs b/test/Test.hs
deleted file mode 100644
--- a/test/Test.hs
+++ /dev/null
@@ -1,59 +0,0 @@
-module Main where
-
-import           Data.Either                    (isRight)
-import           Data.Hashable                  (Hashable)
-import qualified Data.Vector.Unboxed            as Vector
-import           Test.Framework                 (defaultMain, testGroup)
-import           Test.Framework.Providers.HUnit (testCase)
-import           Test.HUnit                     (assertBool, assertEqual)
-
-import qualified Data.PerfectHash.Construction  as Construction
-import qualified Data.PerfectHash.Hashing       as Hashing
-import           Exercise                       (Atom (Atom))
-import qualified Exercise
-
-
-testHashComputation :: String -> Int -> IO ()
-testHashComputation key val =
-  assertEqual error_message val computed_hash
-  where
-    error_message = unwords ["Incorrect hash computation of", key]
-    computed_hash = Hashing.hash 0 key
-
-
-wordIndexTuples = [
-    ("apple", 1 :: Int)
-  , ("banana", 2)
-  , ("carrot", 3)
-  ]
-
-
-intMapTuples :: [(Atom Int, Int)]
-intMapTuples = [
-    (Atom 1000, 1)
-  , (Atom 5555, 2)
-  , (Atom 9876, 3)
-  ]
-
-
-testHashLookups :: (Show (f a), Show b, Eq b, Vector.Unbox b, Construction.Defaultable b, Foldable f, Hashing.ToNumeric a, Eq (f a), Hashable (f a)) =>
-  [(f a, b)] -> IO ()
-testHashLookups word_index_tuples =
-  assertBool "Perfect hash lookups failed to match the input" $ isRight test_result_either
-  where
-    lookup_table = Construction.createMinimalPerfectHash word_index_tuples
-    test_result_either = Exercise.testLookups lookup_table word_index_tuples
-
-
-tests = [
-    testGroup "Hash computation" [
-      testCase "compute-hash1" $ testHashComputation "blarg" 3322346319
-    ]
-  , testGroup "Hash lookups" [
-      testCase "word-lookups" $ testHashLookups wordIndexTuples
-    , testCase "int-lookups" $ testHashLookups intMapTuples
-    ]
-  ]
-
-
-main = defaultMain tests
