diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 0.2.2.0 - July 2024
+
+* Add `sort`, `sortBy`, `sortOn` ([#23](https://github.com/konsumlamm/rrb-vector/pull/22))
+* Fix bug in `><` ([#19](https://github.com/konsumlamm/rrb-vector/pull/19))
+* Fix bug in `<|` ([#18](https://github.com/konsumlamm/rrb-vector/pull/18))
+
 # 0.2.1.0 - December 2023
 
 * Add `findIndexL`, `findIndexR`, `findIndicesL`, `findIndicesR`
diff --git a/bench/Main.hs b/bench/Main.hs
--- a/bench/Main.hs
+++ b/bench/Main.hs
@@ -19,9 +19,16 @@
     , bench "<|" $ whnf (42 RRB.<|) v
     , bench "take" $ whnf (RRB.take idx) v
     , bench "drop" $ whnf (RRB.drop idx) v
+    , bench "splitAt" $ whnf (RRB.splitAt idx) v
+    , bench "insertAt" $ whnf (RRB.insertAt idx 42) v
+    , bench "deleteAt" $ whnf (RRB.deleteAt idx) v
     , bench "index" $ nf (RRB.lookup idx) v
     , bench "adjust" $ whnf (RRB.adjust idx (+ 1)) v
     , bench "map" $ whnf (RRB.map (+ 1)) v
     , bench "foldl" $ nf (foldl (+) 0) v
     , bench "foldr" $ nf (foldr (+) 0) v
+    , bench "findIndexL" $ nf (RRB.findIndexL (== idx)) v
+    , bench "findIndexR" $ nf (RRB.findIndexR (== idx)) v
+    , bench "findIndicesL" $ nf (RRB.findIndicesL (== idx)) v
+    , bench "findIndicesR" $ nf (RRB.findIndicesR (== idx)) v
     ]
diff --git a/bench/Traverse.hs b/bench/Traverse.hs
new file mode 100644
--- /dev/null
+++ b/bench/Traverse.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE NumericUnderscores #-}
+{-# LANGUAGE UnboxedTuples #-}
+
+import Control.Monad (join)
+import Data.Foldable
+import Data.Functor ((<&>))
+
+import qualified Data.RRBVector as RRB
+import Test.Tasty.Bench
+
+default (Int)
+
+main :: IO ()
+main = defaultMain $ [10, 100, 1_000, 10_000, 100_000] <&> \n ->
+    let !v = RRB.fromList [1 .. n]
+    in bgroup (show n)
+        [ bench "Vector" $ whnf (join (<>)) v
+        , bench "List" $ whnf (join (\xs ys -> RRB.fromList (toList xs <> toList ys))) v
+        ]
diff --git a/rrb-vector.cabal b/rrb-vector.cabal
--- a/rrb-vector.cabal
+++ b/rrb-vector.cabal
@@ -1,5 +1,5 @@
 name:               rrb-vector
-version:            0.2.1.0
+version:            0.2.2.0
 synopsis:           Efficient RRB-Vectors
 description:
   An RRB-Vector is an efficient sequence data structure.
@@ -30,8 +30,9 @@
   GHC == 9.0.2
   GHC == 9.2.8
   GHC == 9.4.8
-  GHC == 9.6.3
-  GHC == 9.8.1
+  GHC == 9.6.5
+  GHC == 9.8.2
+  GHC == 9.10.1
 
 source-repository head
   type:     git
@@ -41,13 +42,19 @@
   hs-source-dirs:       src
   exposed-modules:
     Data.RRBVector
-    Data.RRBVector.Internal.Debug
-  other-modules:
     Data.RRBVector.Internal
     Data.RRBVector.Internal.Array
     Data.RRBVector.Internal.Buffer
+    Data.RRBVector.Internal.Debug
+  other-modules:
     Data.RRBVector.Internal.IntRef
-  build-depends:        base >= 4.11 && < 5, deepseq >= 1.4.3 && < 1.6, indexed-traversable ^>= 0.1, primitive >= 0.7 && < 0.10
+    Data.RRBVector.Internal.Sorting
+  build-depends:
+    base >= 4.11 && < 5,
+    deepseq >= 1.4.3 && < 1.6,
+    indexed-traversable ^>= 0.1,
+    primitive >= 0.7.3 && < 0.10,
+    samsort ^>= 0.1
   ghc-options:          -O2 -Wall -Wno-name-shadowing -Werror=missing-methods -Werror=missing-fields
   default-language:     Haskell2010
 
@@ -69,6 +76,15 @@
 benchmark rrb-bench
   hs-source-dirs:       bench
   main-is:              Main.hs
+  type:                 exitcode-stdio-1.0
+  default-language:     Haskell2010
+  ghc-options:          -O2
+  build-depends:        base, rrb-vector, tasty-bench
+  default-extensions:   ExtendedDefaultRules
+
+benchmark traverse
+  hs-source-dirs:       bench
+  main-is:              Traverse.hs
   type:                 exitcode-stdio-1.0
   default-language:     Haskell2010
   ghc-options:          -O2
diff --git a/src/Data/RRBVector.hs b/src/Data/RRBVector.hs
--- a/src/Data/RRBVector.hs
+++ b/src/Data/RRBVector.hs
@@ -31,7 +31,7 @@
     , (!?), (!)
     , update
     , adjust, adjust'
-    , take, drop, splitAt
+    , take, drop, slice, splitAt
     , insertAt, deleteAt
     , findIndexL, findIndexR, findIndicesL, findIndicesR
     -- * With Index
@@ -44,6 +44,10 @@
     , map, map', reverse
     -- * Zipping and unzipping
     , zip, zipWith, unzip, unzipWith
+    -- * Sorting
+    --
+    -- | Currently implemented using [samsort](https://hackage.haskell.org/package/samsort).
+    , sort, sortBy, sortOn
     ) where
 
 import Prelude hiding (replicate, lookup, take, drop, splitAt, map, reverse, zip, zipWith, unzip)
@@ -53,3 +57,4 @@
 import Data.Traversable.WithIndex
 
 import Data.RRBVector.Internal
+import Data.RRBVector.Internal.Sorting
diff --git a/src/Data/RRBVector/Internal.hs b/src/Data/RRBVector/Internal.hs
--- a/src/Data/RRBVector/Internal.hs
+++ b/src/Data/RRBVector/Internal.hs
@@ -22,7 +22,7 @@
     , (!?), (!)
     , update
     , adjust, adjust'
-    , take, drop, splitAt
+    , take, drop, slice, splitAt
     , insertAt, deleteAt
     , findIndexL, findIndexR, findIndicesL, findIndicesR
     -- * Transformations
@@ -174,7 +174,7 @@
           where
             subtree = A.index arr i
 
--- Integer log base 2.
+-- | Integer log base 2.
 log2 :: Int -> Int
 log2 x = bitSizeMinus1 - countLeadingZeros x
   where
@@ -343,7 +343,7 @@
     pure = singleton
     fs <*> xs = foldl' (\acc f -> acc >< map f xs) empty fs
     liftA2 f xs ys = foldl' (\acc x -> acc >< map (f x) ys) empty xs
-    xs *> ys = foldl' (\acc _ -> acc >< ys) empty xs
+    xs *> ys = stimes (length xs) ys
     xs <* ys = foldl' (\acc x -> acc >< replicate (length ys) x) empty xs
 
 instance Monad Vector where
@@ -628,6 +628,10 @@
     | n >= size = empty
     | otherwise = normalize $ Root (size - n) sh (dropTree n sh tree)
 
+-- | \(O(\log n)\). Slice the vector between the two indices (inclusive).
+slice :: (Int, Int) -> Vector a -> Vector a
+slice (i, j) = drop i . take (j + 1)
+
 -- | \(O(\log n)\). Split the vector at the given index.
 --
 -- > splitAt n v = (take n v, drop n v)
@@ -653,20 +657,32 @@
 deleteAt i v = let (left, right) = splitAt (i + 1) v in take i left >< right
 
 -- | \(O(n)\). Find the first index from the left that satisfies the predicate.
+--
+-- @since 0.2.1.0
 findIndexL :: (a -> Bool) -> Vector a -> Maybe Int
 findIndexL f = ifoldr (\i x acc -> if f x then Just i else acc) Nothing
+{-# INLINE findIndexL #-}
 
 -- | \(O(n)\). Find the first index from the right that satisfies the predicate.
+--
+-- @since 0.2.1.0
 findIndexR :: (a -> Bool) -> Vector a -> Maybe Int
 findIndexR f = ifoldl (\i acc x -> if f x then Just i else acc) Nothing
+{-# INLINE findIndexR #-}
 
 -- | \(O(n)\). Find the indices that satisfy the predicate, starting from the left.
+--
+-- @since 0.2.1.0
 findIndicesL :: (a -> Bool) -> Vector a -> [Int]
 findIndicesL f = ifoldr (\i x acc -> if f x then i : acc else acc) []
+{-# INLINE findIndicesL #-}
 
 -- | \(O(n)\). Find the indices that satisfy the predicate, starting from the right.
+--
+-- @since 0.2.1.0
 findIndicesR :: (a -> Bool) -> Vector a -> [Int]
 findIndicesR f = ifoldl (\i acc x -> if f x then i : acc else acc) []
+{-# INLINE findIndicesR #-}
 
 -- concatenation
 
@@ -678,12 +694,9 @@
 Empty >< v = v
 v >< Empty = v
 Root size1 sh1 tree1 >< Root size2 sh2 tree2 =
-    let maxShift = max sh1 sh2
-        upMaxShift = up maxShift
+    let upMaxShift = up (max sh1 sh2)
         newArr = mergeTrees tree1 sh1 tree2 sh2
-    in if length newArr == 1
-        then Root (size1 + size2) maxShift (A.head newArr)
-        else Root (size1 + size2) upMaxShift (computeSizes upMaxShift newArr)
+    in normalize $ Root (size1 + size2) upMaxShift (computeSizes upMaxShift newArr)
   where
     mergeTrees tree1@(Leaf arr1) !_ tree2@(Leaf arr2) !_
         | length arr1 == blockSize = A.from2 tree1 tree2
@@ -768,7 +781,10 @@
     -- compute the shift at which the new branch needs to be inserted (0 means there is space in the leaf)
     -- the size is computed for efficient calculation of the shift in a balanced subtree
     computeShift !sz !sh !min (Balanced _) =
-        let newShift = (log2 sz `div` blockShift) * blockShift
+        -- @sz - 1@ is the index of the last element
+        let hiShift = max ((log2 (sz - 1) `div` blockShift) * blockShift) 0 -- the shift of the root when normalizing
+            hi = (sz - 1) `unsafeShiftR` hiShift -- the length of the root node when normalizing minus 1
+            newShift = if hi < blockMask then hiShift else hiShift + blockShift
         in if newShift > sh then min else newShift
     computeShift _ sh min (Unbalanced arr sizes) =
         let sz' = indexPrimArray sizes 0 -- the size of the first subtree
diff --git a/src/Data/RRBVector/Internal/Array.hs b/src/Data/RRBVector/Internal/Array.hs
--- a/src/Data/RRBVector/Internal/Array.hs
+++ b/src/Data/RRBVector/Internal/Array.hs
@@ -14,7 +14,7 @@
 module Data.RRBVector.Internal.Array
     ( Array, MutableArray
     , ifoldrStep, ifoldlStep, ifoldrStep', ifoldlStep'
-    , empty, singleton, from2
+    , empty, singleton, from2, wrap
     , replicate, replicateSnoc
     , index, head, last
     , update, adjust, adjust'
@@ -121,6 +121,9 @@
     sma <- newSmallArray 2 x
     writeSmallArray sma 1 y
     pure sma
+
+wrap :: SmallArray a -> Array a
+wrap arr = Array 0 (sizeofSmallArray arr) arr
 
 replicate :: Int -> a -> Array a
 replicate n x = Array 0 n $ runSmallArray (newSmallArray n x)
diff --git a/src/Data/RRBVector/Internal/Buffer.hs b/src/Data/RRBVector/Internal/Buffer.hs
--- a/src/Data/RRBVector/Internal/Buffer.hs
+++ b/src/Data/RRBVector/Internal/Buffer.hs
@@ -13,25 +13,29 @@
 
 import Control.Monad.ST
 
+import Data.Primitive.SmallArray
 import Data.RRBVector.Internal.IntRef
 import qualified Data.RRBVector.Internal.Array as A
 
 -- | A mutable array buffer with a fixed capacity.
-data Buffer s a = Buffer !(A.MutableArray s a) !(IntRef s)
+data Buffer s a = Buffer !(SmallMutableArray s a) !(IntRef s)
 
 -- | \(O(n)\). Create a new empty buffer with the given capacity.
 new :: Int -> ST s (Buffer s a)
 new capacity = do
-    buffer <- A.new capacity
+    buffer <- newSmallArray capacity uninitialized
     offset <- newIntRef 0
     pure (Buffer buffer offset)
 
+uninitialized :: a
+uninitialized = errorWithoutStackTrace "uninitialized"
+
 -- | \(O(1)\). Push a new element onto the buffer.
 -- The size of the buffer must not exceed the capacity, but this is not checked.
 push :: Buffer s a -> a -> ST s ()
 push (Buffer buffer offset) x = do
     idx <- readIntRef offset
-    A.write buffer idx x
+    writeSmallArray buffer idx x
     writeIntRef offset (idx + 1)
 
 -- | \(O(n)\). Freeze the content of the buffer and return it.
@@ -39,9 +43,9 @@
 get :: Buffer s a -> ST s (A.Array a)
 get (Buffer buffer offset) = do
     len <- readIntRef offset
-    result <- A.freeze buffer 0 len
+    result <- freezeSmallArray buffer 0 len
     writeIntRef offset 0
-    pure result
+    pure (A.wrap result)
 
 -- | \(O(1)\). Return the current size of the buffer.
 size :: Buffer s a -> ST s Int
diff --git a/src/Data/RRBVector/Internal/Debug.hs b/src/Data/RRBVector/Internal/Debug.hs
--- a/src/Data/RRBVector/Internal/Debug.hs
+++ b/src/Data/RRBVector/Internal/Debug.hs
@@ -10,16 +10,19 @@
     , pattern Empty, pattern Root
     , Tree, Shift
     , pattern Balanced, pattern Unbalanced, pattern Leaf
+    , Invariant, valid
     ) where
 
 import Control.Monad.ST (runST)
-import Data.Foldable (toList)
+import Data.Bits (shiftL)
+import Data.Foldable (foldl', toList, traverse_)
 import Data.List (intercalate)
-import Data.Primitive.PrimArray (PrimArray, primArrayToList)
+import Data.Primitive.PrimArray (PrimArray, primArrayToList, indexPrimArray, sizeofPrimArray)
 
 import Data.RRBVector.Internal hiding (Empty, Root, Balanced, Unbalanced, Leaf)
 import qualified Data.RRBVector.Internal as RRB
 import Data.RRBVector.Internal.Array (Array)
+import qualified Data.RRBVector.Internal.Array as A
 import qualified Data.RRBVector.Internal.Buffer as Buffer
 
 -- | \(O(n)\). Show the underlying tree of a vector.
@@ -85,3 +88,113 @@
 pattern Leaf arr <- RRB.Leaf arr
 
 {-# COMPLETE Balanced, Unbalanced, Leaf #-}
+
+-- | Structural invariants a vector is expected to hold.
+data Invariant
+    = RootSizeGt0      -- Root: Size > 0
+    | RootShiftDiv     -- Root: The shift at the root is divisible by blockShift
+    | RootSizeCorrect  -- Root: The size at the root is correct
+    | RootGt1Child     -- Root: The root has more than 1 child if not a Leaf
+    | BalShiftGt0      -- Balanced: Shift > 0
+    | BalNumChildren   -- Balanced: The number of children is blockSize unless
+                       -- the parent is unbalanced or the node is on the right
+                       -- edge in which case it is in [1,blockSize]
+    | BalFullChildren  -- Balanced: All children are full, except for the last
+                       -- if the node is on the right edge
+    | UnbalShiftGt0    -- Unbalanced: Shift > 0
+    | UnbalParentUnbal -- Unbalanced: Parent is Unbalanced
+    | UnbalNumChildren -- Unbalanced: The number of children is in [1,blockSize]
+    | UnbalSizes       -- Unbalanced: The sizes array is correct
+    | UnbalNotBal      -- Unbalanced: The tree is not full enough to be a
+                       -- Balanced
+    | LeafShift0       -- Leaf: Shift == 0
+    | LeafNumElems     -- Leaf: The number of elements is in [1,blockSize]
+    deriving Show
+
+assert :: Invariant -> Bool -> Either Invariant ()
+assert i False = Left i
+assert _ True = pure ()
+
+-- | Check tree invariants. Returns @Left@ on finding a violated invariant.
+valid :: Vector a -> Either Invariant ()
+valid RRB.Empty = pure ()
+valid (RRB.Root size sh tree) = do
+    assert RootSizeGt0 $ size > 0
+    assert RootShiftDiv $ sh `mod` blockShift == 0
+    assert RootSizeCorrect $ size == countElems tree
+    assert RootGt1Child $ case tree of
+        Balanced arr -> length arr > 1
+        Unbalanced arr _ -> length arr > 1
+        Leaf _ -> True
+    validTree Unbal sh tree
+
+data NodeDesc
+    = Bal           -- parent is Balanced
+    | BalRightEdge  -- parent is Balanced and this node is on the right edge
+    | Unbal         -- parent is Unbalanced
+
+validTree :: NodeDesc -> Shift -> Tree a -> Either Invariant ()
+validTree desc sh (RRB.Balanced arr) = do
+    assert BalShiftGt0 $ sh > 0
+    assert BalNumChildren $ case desc of
+        Bal -> n == blockSize
+        BalRightEdge -> n >= 1 && n <= blockSize
+        Unbal -> n >= 1 && n <= blockSize
+    assert BalFullChildren $
+        all (\t -> countElems t == 1 `shiftL` sh) expectedFullChildren
+    traverse_ (validTree Bal (down sh)) arrInit
+    validTree descLast (down sh) (A.last arr)
+  where
+    n = length arr
+    arrInit = A.take arr (n-1)
+    expectedFullChildren = case desc of
+        Bal -> arr
+        BalRightEdge -> arrInit
+        Unbal -> arrInit
+    descLast = case desc of
+        Bal -> Bal
+        BalRightEdge -> BalRightEdge
+        Unbal -> BalRightEdge
+validTree desc sh (RRB.Unbalanced arr sizes) = do
+    assert UnbalShiftGt0 $ sh > 0
+    case desc of
+        Bal -> assert UnbalParentUnbal False
+        BalRightEdge -> assert UnbalParentUnbal False
+        Unbal -> assert UnbalNumChildren $ n >= 1 && n <= blockSize
+    assert UnbalSizes $ n == sizeofPrimArray sizes
+    assert UnbalSizes $
+        all (\i -> countElems (A.index arr i) == getSize sizes i) [0 .. n-1]
+    assert UnbalNotBal $ not (couldBeBalanced sh arr sizes)
+    traverse_ (validTree Unbal (down sh)) arr
+  where
+    n = length arr
+validTree desc sh (RRB.Leaf arr) = do
+    assert LeafShift0 $ sh == 0
+    assert LeafNumElems $ case desc of
+        Bal -> n == blockSize
+        BalRightEdge -> n >= 1 && n <= blockSize
+        Unbal -> n >= 1 && n <= blockSize
+  where
+    n = length arr
+
+-- | Check whether an Unbalanced node could be Balanced.
+couldBeBalanced :: Shift -> A.Array (Tree a) -> PrimArray Int -> Bool
+couldBeBalanced sh arr sizes =
+   all (\i -> getSize sizes i == 1 `shiftL` sh) [0 .. n-2] &&
+   (case A.last arr of
+       Balanced _ -> True
+       Unbalanced arr' sizes' -> couldBeBalanced (down sh) arr' sizes'
+       Leaf _ -> True)
+  where
+    n = length arr
+
+getSize :: PrimArray Int -> Int -> Int
+getSize sizes 0 = indexPrimArray sizes 0
+getSize sizes i = indexPrimArray sizes i - indexPrimArray sizes (i-1)
+
+countElems :: Tree a -> Int
+countElems (RRB.Balanced arr) =
+    foldl' (\acc tree -> acc + countElems tree) 0 arr
+countElems (RRB.Unbalanced arr _) =
+    foldl' (\acc tree -> acc + countElems tree) 0 arr
+countElems (RRB.Leaf arr) = length arr
diff --git a/src/Data/RRBVector/Internal/Sorting.hs b/src/Data/RRBVector/Internal/Sorting.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/RRBVector/Internal/Sorting.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE MagicHash #-}
+
+module Data.RRBVector.Internal.Sorting
+    ( sort
+    , sortBy
+    , sortOn
+    ) where
+
+import Data.Foldable (toList)
+import Data.Foldable.WithIndex (ifor_)
+import Data.Primitive.Array
+import Data.SamSort (sortArrayBy)
+import Data.Semigroup (Arg(..))
+
+import Data.RRBVector.Internal
+
+uninitialized :: a
+uninitialized = errorWithoutStackTrace "uninitialized"
+
+-- | \(O(n \log n)\). Sort the vector in ascending order.
+-- The sort is stable, meaning the order of equal elements is preserved.
+--
+-- @since 0.2.2.0
+sort :: (Ord a) => Vector a -> Vector a
+sort = sortBy compare
+
+-- | \(O(n \log n)\). Sort the vector in ascending order according to the specified comparison function.
+-- The sort is stable, meaning the order of equal elements is preserved.
+--
+-- @since 0.2.2.0
+sortBy :: (a -> a -> Ordering) -> Vector a -> Vector a
+sortBy cmp v =
+    let sortedArr = createArray (length v) uninitialized $ \arr@(MutableArray arr#) -> do
+            ifor_ v (writeArray arr)
+            sortArrayBy cmp arr# 0 (length v)
+    in fromList . toList $ sortedArr
+
+-- | \(O(n \log n)\). Sort the vector in ascending order by comparing the results of applying the key function to each element.
+-- The sort is stable, meaning the order of equal elements is preserved.
+-- @`sortOn` f@ is equivalent to @`sortBy` (`Data.Ord.comparing` f)@, but only evaluates @f@ once for each element.
+--
+-- @since 0.2.2.0
+sortOn :: (Ord b) => (a -> b) -> Vector a -> Vector a
+sortOn f v =
+    let sortedArr = createArray (length v) uninitialized $ \arr@(MutableArray arr#) -> do
+            ifor_ v $ \i x -> let !y = f x in writeArray arr i (Arg y x)
+            sortArrayBy compare arr# 0 (length v)
+    in fromList . fmap (\(Arg _ x) -> x) . toList $ sortedArr
diff --git a/test/Arbitrary.hs b/test/Arbitrary.hs
--- a/test/Arbitrary.hs
+++ b/test/Arbitrary.hs
@@ -1,32 +1,64 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase #-}
 
 module Arbitrary where
 
 #if !(MIN_VERSION_base(4,18,0))
 import Control.Applicative (liftA2)
 #endif
-import Data.Foldable (toList)
+import Control.Monad.ST
+import Data.Bool (bool)
+import Debug.Trace
 
 import Test.Tasty.QuickCheck
 
-import qualified Data.RRBVector as V
+import qualified Data.RRBVector.Internal as V
+import qualified Data.RRBVector.Internal.Buffer as Buffer
 import Data.RRBVector.Internal.Debug
 
-builders :: [[a] -> V.Vector a]
-builders = [V.fromList, fromListUnbalanced]
+arbitraryVectorOf :: Gen a -> Gen (V.Vector a)
+arbitraryVectorOf gen = sized $ \n -> do
+    xs <- vectorOf n gen
+    if n <= V.blockSize
+        then pure $ V.fromList xs
+        else nodes V.Leaf xs >>= \case
+            [tree] -> pure $ V.Root (V.treeSize 0 tree) 0 tree
+            ts -> iterateNodes V.blockShift ts
+  where
+    nodes f trees = do
+        sizes <- infiniteListOf (arbitrary >>= bool (pure V.blockSize) (choose (1, V.blockSize - 1)))
+        pure $ runST $ do
+            buffer <- Buffer.new V.blockSize
+            let loop _ [] = do
+                    result <- Buffer.get buffer
+                    pure [f result]
+                loop (sz : sizes') (t : ts) = do
+                    size <- Buffer.size buffer
+                    if size == sz then do
+                        result <- Buffer.get buffer
+                        Buffer.push buffer t
+                        rest <- loop sizes' ts
+                        pure (f result : rest)
+                    else do
+                        Buffer.push buffer t
+                        loop (sz : sizes') ts
+            loop sizes trees
+    {-# INLINE nodes #-}
 
+    iterateNodes sh trees = do
+        ts <- nodes (V.computeSizes sh) trees
+        case ts of
+            [tree] -> pure $ V.Root (V.treeSize sh tree) sh tree
+            trees' -> iterateNodes (V.up sh) trees'
+
 instance (Arbitrary a) => Arbitrary (V.Vector a) where
     arbitrary = arbitrary1
     shrink = shrink1
 
 -- TODO: improve instance
 instance Arbitrary1 V.Vector where
-    liftArbitrary gen = do
-        build <- elements builders
-        fmap build (liftArbitrary gen)
-
-    liftShrink shr = concatMap (sequence builders) . liftShrink shr . toList
+    liftArbitrary = arbitraryVectorOf
 
 -- A custom 'Testable' instance to use 'showTree'.
 instance {-# OVERLAPPING #-} (Arbitrary a, Show a, Testable prop) => Testable (V.Vector a -> prop) where
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,6 +1,10 @@
+import Data.Foldable (for_)
 import Test.Tasty
 import Test.Tasty.QuickCheck
 
+import Data.RRBVector
+import Data.RRBVector.Internal.Debug
+
 import Properties (properties)
 import Strictness (strictness)
 
@@ -9,3 +13,8 @@
     [ properties
     , strictness
     ]
+
+main' :: IO ()
+main' = do
+    vs <- sample' (arbitrary :: Gen (Vector Int))
+    for_ vs $ \v -> putStrLn (showTree v)
diff --git a/test/Properties.hs b/test/Properties.hs
--- a/test/Properties.hs
+++ b/test/Properties.hs
@@ -10,12 +10,14 @@
 import Control.Applicative (liftA2)
 #endif
 import Data.Foldable (Foldable(..))
-import Data.List (uncons)
+import Data.List (sort, sortBy, sortOn, uncons)
+import Data.Ord (comparing)
 import Data.Proxy (Proxy(..))
 import Prelude hiding ((==)) -- use @===@ instead
 
 import qualified Data.Sequence as Seq
 import qualified Data.RRBVector as V
+import qualified Data.RRBVector.Internal.Debug as VDebug
 import Test.QuickCheck.Classes.Base
 import Test.Tasty
 import Test.Tasty.QuickCheck
@@ -65,6 +67,13 @@
 proxyV :: Proxy V
 proxyV = Proxy
 
+checkValid :: Show a => V a -> Property
+checkValid v = case VDebug.valid v of
+    Left invariant ->
+        counterexample ("Invariant violated: " ++ show invariant) $
+        counterexample (VDebug.showTree v) False
+    _ -> property ()
+
 properties :: TestTree
 properties = testGroup "properties"
     [ testGroup "fromList"
@@ -72,22 +81,27 @@
         , testProperty "satisfies `toList . fromList = id`" $ \ls -> toList (V.fromList ls) === ls
         , testProperty "satisfies `fromList [] = empty`" $ V.fromList [] === V.empty
         , testProperty "satisfies `fromList [x] = singleton x`" $ \x -> V.fromList [x] === V.singleton x
+        , testProperty "valid" $ \xs -> checkValid (V.fromList xs)
         ]
     , testGroup "replicate"
         [ testProperty "satisifes `replicate n == fromList . replicate n`" $ \(Positive n) x -> V.replicate n x === V.fromList (replicate n x)
         , testProperty "returns the empty vector for non-positive n" $ \(NonPositive n) x -> V.replicate n x === V.empty
+        , testProperty "valid" $ \n x -> checkValid (V.replicate n x)
         ]
     , testGroup "<|"
         [ testProperty "prepends an element" $ \x v -> toList (x V.<| v) === x : toList v
         , testProperty "works for the empty vector" $ \x -> x V.<| V.empty === V.singleton x
+        , testProperty "valid" $ \x v -> checkValid (x V.<| v)
         ]
     , testGroup "|>"
         [ testProperty "appends an element" $ \v x -> toList (v V.|> x) === toList v ++ [x]
         , testProperty "works for the empty vector" $ \x -> V.empty V.|> x === V.singleton x
+        , testProperty "valid" $ \v x -> checkValid (v V.|> x)
         ]
     , testGroup "><"
         [ testProperty "concatenates two vectors" $ \v1 v2 -> toList (v1 V.>< v2) === toList v1 ++ toList v2
         , testProperty "works for the empty vector" $ \v -> (V.empty V.>< v === v) .&&. (v V.>< V.empty === v)
+        , testProperty "valid" $ \v1 v2 -> checkValid (v1 V.>< v2)
         ]
     , testGroup "lookup"
         [ testProperty "gets the element at the index" $ \v (NonNegative i) -> V.lookup i v === lookupList i (toList v)
@@ -96,33 +110,41 @@
     , testGroup "update"
         [ testProperty "updates the element at the index" $ \v (NonNegative i) x -> toList (V.update i x v) === updateList i x (toList v)
         , testProperty "returns the vector for negative indices" $ \v (Negative i) x -> V.update i x v === v
+        , testProperty "valid" $ \v i x -> checkValid (V.update i x v)
         ]
     , testGroup "adjust"
         [ testProperty "adjusts the element at the index" $ \v (NonNegative i) (Fn f) -> toList (V.adjust i f v) === adjustList i f (toList v)
         , testProperty "returns the vector for negative indices" $ \v (Negative i) (Fn f) -> V.adjust i f v === v
+        , testProperty "valid" $ \v i (Fn f) -> checkValid (V.adjust i f v)
         ]
     , testGroup "adjust'"
         [ testProperty "adjusts the element at the index" $ \v (NonNegative i) (Fn f) -> toList (V.adjust' i f v) === adjustList i f (toList v)
         , testProperty "returns the vector for negative indices" $ \v (Negative i) (Fn f) -> V.adjust' i f v === v
+        , testProperty "valid" $ \v i (Fn f) -> checkValid (V.adjust' i f v)
         ]
     , testGroup "viewl"
         [ testProperty "works like uncons" $ \v -> fmap (\(x, xs) -> (x, toList xs)) (V.viewl v) === uncons (toList v)
         , testProperty "works for the empty vector" $ V.viewl V.empty === Nothing
+        , testProperty "valid" $ \v -> fmap (checkValid . snd) (V.viewl v)
         ]
     , testGroup "viewr"
         [ testProperty "works like unsnoc" $ \v -> fmap (\(xs, x) -> (toList xs, x)) (V.viewr v) === unsnoc (toList v)
         , testProperty "works for the empty vector" $ V.viewr V.empty === Nothing
+        , testProperty "valid" $ \v -> fmap (checkValid . fst) (V.viewr v)
         ]
     , testGroup "take"
         [ testProperty "takes n elements" $ \v (Positive n) -> toList (V.take n v) === take n (toList v)
         , testProperty "returns the empty vector for non-positive n" $ \v (NonPositive n) -> V.take n v === V.empty
+        , testProperty "valid" $ \v n -> checkValid (V.take n v)
         ]
     , testGroup "drop"
         [ testProperty "drops n elements" $ \v (Positive n) -> toList (V.drop n v) === drop n (toList v)
         , testProperty "returns the vector for non-positive n" $ \v (NonPositive n) -> V.drop n v === v
+        , testProperty "valid" $ \v n -> checkValid (V.drop v n)
         ]
     , testGroup "splitAt"
         [ testProperty "splits the vector" $ \v n -> let (v1, v2) = V.splitAt n v in (toList v1, toList v2) === splitAt n (toList v)
+        , testProperty "valid" $ \v n -> let (v1, v2) = V.splitAt n v in checkValid v1 .&&. checkValid v2
         ]
     , testGroup "insertAt"
         [ testProperty "inserts an element" $ \v i x -> toList (V.insertAt i x v) === insertAtList i x (toList v)
@@ -130,6 +152,7 @@
         , testProperty "appends for too large indices" $ \v x -> forAll (arbitrary `suchThat` (> length v)) $ \i -> V.insertAt i x v === v V.|> x
         , testProperty "satisfies `insertAt 0 x v = x <| v`" $ \v x -> V.insertAt 0 x v === x V.<| v
         , testProperty "satisfies `insertAt (length v) x v = v |> x`" $ \v x -> V.insertAt (length v) x v === v V.|> x
+        , testProperty "valid" $ \v i x -> checkValid (V.insertAt i x v)
         ]
     , testGroup "deleteAt"
         [ testProperty "deletes an element" $ \v (NonNegative i) -> toList (V.deleteAt i v) === deleteAtList i (toList v)
@@ -137,6 +160,7 @@
         , testProperty "returns the vector for too large indices" $ \v -> forAll (arbitrary `suchThat` (>= length v)) $ \i -> V.deleteAt i v === v
         , testProperty "satisfies `deleteAt 0 v = drop 1 v`" $ \v -> V.deleteAt 0 v === V.drop 1 v
         , testProperty "satisfies `deleteAt (length v - 1) v = take (length v - 1) v`" $ \v -> V.deleteAt (length v - 1) v === V.take (length v - 1) v
+        , testProperty "valid" $ \v i -> checkValid (V.deleteAt i v)
         ]
     , testGroup "findIndexL"
         [ testProperty "finds the first index" $ \v (Fn f) -> V.findIndexL f v === Seq.findIndexL f (Seq.fromList (toList v))
@@ -156,16 +180,25 @@
         ]
     , testGroup "reverse"
         [ testProperty "reverses the vector" $ \v -> toList (V.reverse v) === reverse (toList v)
+        , testProperty "valid" $ \v -> checkValid (V.reverse v)
         ]
     , testGroup "zip"
         [ testProperty "zips two vectors" $ \v1 v2 -> toList (V.zip v1 v2) === zip (toList v1) (toList v2)
+        , testProperty "valid" $ \v1 v2 -> checkValid (V.zip v1 v2)
         ]
     , testGroup "zipWith"
         [ testProperty "zips two vectors with a function" $ \v1 v2 -> toList (V.zipWith (+) v1 v2) === zipWith (+) (toList v1) (toList v2)
         , testProperty "satisfies `zipWith (,) v1 v2 = zip v1 v2`" $ \v1 v2 -> V.zipWith (,) v1 v2 === V.zip v1 v2
+        , testProperty "valid" $ \v1 v2 (Fn2 f) -> checkValid (V.zipWith f v1 v2)
         ]
     , testGroup "unzip"
         [ testProperty "unzips the vector" $ \v -> (\(xs, ys) -> (toList xs, toList ys)) (V.unzip v) === unzip (toList v)
+        , testProperty "valid" $ \v -> let (v1, v2) = V.unzip v in checkValid v1 .&&. checkValid v2
+        ]
+    , localOption (QuickCheckMaxSize 1000) $ testGroup "sorting"
+        [ testProperty "sort" $ \v -> toList (V.sort v) === sort (toList v)
+        , testProperty "sortBy" $ \v -> let cmp = comparing fst in toList (V.sortBy cmp v) === sortBy cmp (toList v)
+        , testProperty "sortOn" $ \v -> let f = odd in toList (V.sortOn f v) === sortOn f (toList v)
         ]
     , instances
     , laws
