diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.3.1
+
+ * Do not call next/prev on invalid iterators (fixes assertion failure).
+
 ## 2.3.0
 
  * Remove UnliftIO and remain in IO monad.
diff --git a/rocksdb-haskell-jprupp.cabal b/rocksdb-haskell-jprupp.cabal
--- a/rocksdb-haskell-jprupp.cabal
+++ b/rocksdb-haskell-jprupp.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           rocksdb-haskell-jprupp
-version:        2.3.0
+version:        2.3.1
 synopsis:       Haskell bindings for RocksDB
 description:    See README at <https://github.com/jprupp/rocksdb-haskell#readme>
 category:       Database, FFI
diff --git a/src/Database/RocksDB/Iterator.hs b/src/Database/RocksDB/Iterator.hs
--- a/src/Database/RocksDB/Iterator.hs
+++ b/src/Database/RocksDB/Iterator.hs
@@ -38,6 +38,7 @@
 where
 
 import Control.Exception (bracket)
+import Control.Monad (when)
 import Data.ByteString (ByteString)
 import Data.ByteString qualified as B
 import Data.ByteString.Unsafe qualified as BU
@@ -188,20 +189,24 @@
 -- | Moves to the next entry in the source. After this call, 'iterValid' is
 -- /true/ iff the iterator was not positioned at the last entry in the source.
 --
--- If the iterator is not valid, this function does nothing. Note that this is a
--- shortcoming of the C API: an 'iterPrev' might still be possible, but we can't
--- determine if we're at the last or first entry.
+-- If the iterator is not valid, this function does nothing: RocksDB does not
+-- allow moving an invalid iterator, so reposition it with 'iterSeek',
+-- 'iterFirst' or 'iterLast' instead.
 iterNext :: Iterator -> IO ()
-iterNext = c_rocksdb_iter_next
+iterNext it = do
+    valid <- iterValid it
+    when valid $ c_rocksdb_iter_next it
 
 -- | Moves to the previous entry in the source. After this call, 'iterValid' is
 -- /true/ iff the iterator was not positioned at the first entry in the source.
 --
--- If the iterator is not valid, this function does nothing. Note that this is a
--- shortcoming of the C API: an 'iterNext' might still be possible, but we can't
--- determine if we're at the last or first entry.
+-- If the iterator is not valid, this function does nothing: RocksDB does not
+-- allow moving an invalid iterator, so reposition it with 'iterSeek',
+-- 'iterFirst' or 'iterLast' instead.
 iterPrev :: Iterator -> IO ()
-iterPrev = c_rocksdb_iter_prev
+iterPrev it = do
+    valid <- iterValid it
+    when valid $ c_rocksdb_iter_prev it
 
 -- | Return the key for the current entry if the iterator is currently
 -- positioned at an entry, ie. 'iterValid'.
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -105,9 +105,9 @@
           iterPrev itr -- Invalid before lowest key
           iterKey itr `shouldReturn` Nothing
           iterValue itr `shouldReturn` Nothing
-          iterNext itr -- But it remembers previous position
-          iterKey itr `shouldReturn` Just "b"
-          iterValue itr `shouldReturn` Just "bbb"
+          iterNext itr -- Invalid iterator stays invalid
+          iterKey itr `shouldReturn` Nothing
+          iterValue itr `shouldReturn` Nothing
     describe "Snapshots" $ do
       it "getCF and iterator see same data during concurrent writes" $ \db -> do
         let cf = head $ columnFamilies db
