diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for byteslice
 
+## 0.1.3.0 -- 2019-09-15
+
+* Add `isPrefixOf` and `isSuffixOf`.
+* Add `foldl`, `foldr`, `foldl'`, and `foldr'`.
+
 ## 0.1.2.0 -- 2019-08-21
 
 * Add `Data.Bytes.Mutable` module.
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: byteslice
-version: 0.1.2.0
+version: 0.1.3.0
 synopsis: Slicing managed and unmanaged memory
 description:
   This library provides types that allow the user to talk about a slice of
@@ -23,10 +23,24 @@
     Data.Bytes.Mutable
     Data.Bytes.Types
   build-depends:
-    , base >=4.11.1.0 && <5
+    , base >=4.11.1 && <5
     , primitive >=0.7 && <0.8
     , primitive-addr >=0.1 && <0.2
     , run-st >=0.1 && <0.2
   hs-source-dirs: src
   ghc-options: -Wall -O2
   default-language: Haskell2010
+
+test-suite test
+  default-language: Haskell2010
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: Main.hs
+  ghc-options: -Wall -O2
+  build-depends:
+    , base >=4.11.1 && <5
+    , byteslice
+    , primitive
+    , tasty-hunit
+    , tasty
+    , tasty-quickcheck
diff --git a/src/Data/Bytes.hs b/src/Data/Bytes.hs
--- a/src/Data/Bytes.hs
+++ b/src/Data/Bytes.hs
@@ -1,4 +1,5 @@
 {-# language BangPatterns #-}
+{-# language MagicHash #-}
 {-# language TypeApplications #-}
 
 module Data.Bytes
@@ -10,6 +11,14 @@
     -- * Filtering
   , takeWhile
   , dropWhile
+    -- * Folds
+  , foldl
+  , foldl'
+  , foldr
+  , foldr'
+    -- * Equality
+  , isPrefixOf
+  , isSuffixOf
     -- * Unsafe Slicing
   , unsafeTake
   , unsafeDrop
@@ -20,13 +29,14 @@
   , fromByteArray
   ) where
 
-import Prelude hiding (length,takeWhile,dropWhile,null)
+import Prelude hiding (length,takeWhile,dropWhile,null,foldl,foldr)
 
 import Data.Bytes.Types (Bytes(Bytes))
-import Data.Primitive (ByteArray)
+import Data.Primitive (ByteArray(ByteArray))
 import Data.Word (Word8)
 import Data.Char (ord)
 import Control.Monad.ST.Run (runByteArrayST)
+import GHC.Exts (Int(I#))
 
 import qualified Data.Primitive as PM
 import qualified GHC.Exts as Exts
@@ -39,6 +49,25 @@
 length :: Bytes -> Int
 length (Bytes _ _ len) = len
 
+-- | Is the first argument a prefix of the second argument?
+isPrefixOf :: Bytes -> Bytes -> Bool
+isPrefixOf (Bytes a aOff aLen) (Bytes b bOff bLen) =
+  -- For prefix and suffix testing, we do not use
+  -- the sameByteArray optimization that we use in
+  -- the Eq instance. Prefix and suffix testing seldom 
+  -- compares a byte array with the same in-memory
+  -- byte array.
+  if aLen <= bLen
+    then compareByteArrays a aOff b bOff aLen == EQ
+    else False
+
+-- | Is the first argument a suffix of the second argument?
+isSuffixOf :: Bytes -> Bytes -> Bool
+isSuffixOf (Bytes a aOff aLen) (Bytes b bOff bLen) =
+  if aLen <= bLen
+    then compareByteArrays a aOff b (bOff + bLen - aLen) aLen == EQ
+    else False
+
 -- | Take bytes while the predicate is true.
 takeWhile :: (Word8 -> Bool) -> Bytes -> Bytes
 {-# inline takeWhile #-}
@@ -74,6 +103,42 @@
       else n
     else n
 
+-- | Left fold over bytes, non-strict in the accumulator.
+foldl :: (a -> Word8 -> a) -> a -> Bytes -> a
+{-# inline foldl #-}
+foldl f a0 (Bytes arr off0 len0) =
+  go (off0 + len0 - 1) (len0 - 1) 
+  where
+  go !off !ix = case ix of
+    (-1) -> a0
+    _ -> f (go (off - 1) (ix - 1)) (PM.indexByteArray arr off)
+
+-- | Right fold over bytes, non-strict in the accumulator.
+foldr :: (Word8 -> a -> a) -> a -> Bytes -> a
+{-# inline foldr #-}
+foldr f a0 (Bytes arr off0 len0) = go off0 len0 where
+  go !off !len = case len of
+    0 -> a0
+    _ -> f (PM.indexByteArray arr off) (go (off + 1) (len - 1))
+
+-- | Left fold over bytes, strict in the accumulator.
+foldl' :: (a -> Word8 -> a) -> a -> Bytes -> a
+{-# inline foldl' #-}
+foldl' f a0 (Bytes arr off0 len0) = go a0 off0 len0 where
+  go !a !off !len = case len of
+    0 -> a
+    _ -> go (f a (PM.indexByteArray arr off)) (off + 1) (len - 1)
+
+-- | Right fold over bytes, strict in the accumulator.
+foldr' :: (Word8 -> a -> a) -> a -> Bytes -> a
+{-# inline foldr' #-}
+foldr' f a0 (Bytes arr off0 len0) =
+  go a0 (off0 + len0 - 1) (len0 - 1) 
+  where
+  go !a !off !ix = case ix of
+    (-1) -> a
+    _ -> go (f (PM.indexByteArray arr off) a) (off - 1) (ix - 1)
+
 -- | Convert the sliced 'Bytes' to an unsliced 'ByteArray'. This
 -- reuses the array backing the sliced 'Bytes' if the slicing metadata
 -- implies that all of the bytes are used. Otherwise, it makes a copy.
@@ -101,3 +166,7 @@
 fromByteArray :: ByteArray -> Bytes
 fromByteArray b = Bytes b 0 (PM.sizeofByteArray b)
 
+compareByteArrays :: ByteArray -> Int -> ByteArray -> Int -> Int -> Ordering
+{-# INLINE compareByteArrays #-}
+compareByteArrays (ByteArray ba1#) (I# off1#) (ByteArray ba2#) (I# off2#) (I# n#) =
+  compare (I# (Exts.compareByteArrays# ba1# off1# ba2# off2# n#)) 0
diff --git a/src/Data/Bytes/Types.hs b/src/Data/Bytes/Types.hs
--- a/src/Data/Bytes/Types.hs
+++ b/src/Data/Bytes/Types.hs
@@ -76,14 +76,12 @@
   else unsafeChr (ord 'a' + (fromIntegral w) - 10)
 
 instance Eq Bytes where
-  {-# INLINE (==) #-}
   Bytes arr1 off1 len1 == Bytes arr2 off2 len2
     | len1 /= len2 = False
     | sameByteArray arr1 arr2 && off1 == off2 = True
     | otherwise = compareByteArrays arr1 off1 arr2 off2 len1 == EQ
 
 instance Ord Bytes where
-  {-# INLINE compare #-}
   compare (Bytes arr1 off1 len1) (Bytes arr2 off2 len2)
     | sameByteArray arr1 arr2 && off1 == off2 && len1 == len2 = EQ
     | otherwise = compareByteArrays arr1 off1 arr2 off2 len1
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,64 @@
+{-# language BangPatterns #-}
+{-# language MultiWayIf #-}
+{-# language NumDecimals #-}
+{-# language OverloadedStrings #-}
+{-# language ScopedTypeVariables #-}
+{-# language TypeApplications #-}
+
+import Data.Primitive (ByteArray)
+import Data.Word (Word8)
+import Data.Char (ord)
+import Data.Bytes.Types (Bytes(Bytes))
+import Test.Tasty (defaultMain,testGroup,TestTree)
+import Test.Tasty.HUnit ((@=?),testCase)
+import Test.Tasty.QuickCheck ((===),testProperty)
+
+import qualified Data.Bytes as Bytes
+import qualified Data.Foldable as Foldable
+import qualified Data.List as List
+import qualified Data.Primitive as PM
+import qualified GHC.Exts as Exts
+import qualified Test.Tasty.HUnit as THU
+import qualified Test.Tasty.QuickCheck as QC
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: TestTree
+tests = testGroup "Bytes"
+  [ testGroup "isPrefixOf"
+    [ testCase "A" $ THU.assertBool "" $
+        Bytes.isPrefixOf (bytes "hey") (bytes "hey man")
+    , testCase "B" $ THU.assertBool "" $
+        not (Bytes.isPrefixOf (bytes "an") (bytes "hey man"))
+    ]
+  , testGroup "isSuffixOf"
+    [ testCase "A" $ THU.assertBool "" $
+        Bytes.isSuffixOf (bytes "an") (bytes "hey man")
+    , testCase "B" $ THU.assertBool "" $
+        not (Bytes.isSuffixOf (bytes "h") (bytes "hey man"))
+    ]
+  , testProperty "foldl" $ \(x :: Word8) (xs :: [Word8]) ->
+      List.foldl (-) 0 xs
+      ===
+      Bytes.foldl (-) 0 (Bytes.unsafeDrop 1 (Exts.fromList (x : xs)))
+  , testProperty "foldl'" $ \(x :: Word8) (xs :: [Word8]) ->
+      List.foldl' (-) 0 xs
+      ===
+      Bytes.foldl' (-) 0 (Bytes.unsafeDrop 1 (Exts.fromList (x : xs)))
+  , testProperty "foldr" $ \(x :: Word8) (xs :: [Word8]) ->
+      Foldable.foldr (-) 0 xs
+      ===
+      Bytes.foldr (-) 0 (Bytes.unsafeDrop 1 (Exts.fromList (x : xs)))
+  , testProperty "foldr'" $ \(x :: Word8) (xs :: [Word8]) ->
+      Foldable.foldr' (-) 0 xs
+      ===
+      Bytes.foldr' (-) 0 (Bytes.unsafeDrop 1 (Exts.fromList (x : xs)))
+  ]
+
+bytes :: String -> Bytes
+bytes s = let b = pack ('x' : s) in Bytes b 1 (PM.sizeofByteArray b - 1)
+
+pack :: String -> ByteArray
+pack = Exts.fromList . map (fromIntegral @Int @Word8 . ord)
+
