diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,16 @@
 
 `hslua-list` uses [PVP Versioning](https://pvp.haskell.org).
 
+## hslua-list-1.1.4
+
+Released 2024-10-01.
+
+-   Added function `List.iter`; it returns an iterator function
+    that returns the next list item each time it is called.
+
+-   `List.extend` now returns the extended list instead of the
+    list that was appended.
+
 ## hslua-list-1.1.3
 
 Released 2024-09-26.
diff --git a/cbits/listmod.c b/cbits/listmod.c
--- a/cbits/listmod.c
+++ b/cbits/listmod.c
@@ -198,6 +198,7 @@
     lua_geti(L, 2, i);
     lua_seti(L, 1, len1 + i);
   }
+  lua_pop(L, 1); /* remove the appended table */
   return 1;
 }
 
@@ -291,6 +292,41 @@
 }
 
 /*
+** Closure that returns the next element in the list each time it's called.
+*/
+static int list_item_iterator(lua_State *L) {
+  lua_Integer step = lua_tointeger(L, lua_upvalueindex(2));
+  lua_Integer i = lua_tointeger(L, lua_upvalueindex(3));
+  lua_geti(L, lua_upvalueindex(1), i);
+  lua_pushinteger(L, i + step);
+  lua_replace(L, lua_upvalueindex(3));
+  return 1;
+}
+
+/*
+** Returns an iterator triple that returns all values, but not the keys.
+*/
+static int list_iter(lua_State *L) {
+  lua_settop(L, 3);
+  luaL_checktype(L, 1, LUA_TTABLE);
+  lua_Integer step = luaL_optinteger(L, 2, 1);
+  lua_Integer len = luaL_len(L, 1);
+  lua_Integer start = luaL_optinteger(L, 3, (step > 0 || len <= 0) ? 1 : len);
+
+  if (step == 0) {
+    lua_pushstring(L, "List.iter: step size must not be 0");
+    return lua_error(L);
+  }
+
+  luaL_checkstack(L, 3, "List.iter");
+  lua_pushvalue(L, 1);
+  lua_pushinteger(L, step);
+  lua_pushinteger(L, start);
+  lua_pushcclosure(L, list_item_iterator, 3);
+  return 1;
+}
+
+/*
 ** Returns a copy of the current list by applying the given function to
 ** all elements.
 */
@@ -399,6 +435,7 @@
   {"find_if", list_find_if},
   {"includes", list_includes},
   {"insert", missing},
+  {"iter", list_iter},
   {"map", list_map},
   {"new", list_new},
   {"remove", missing},
diff --git a/hslua-list.cabal b/hslua-list.cabal
--- a/hslua-list.cabal
+++ b/hslua-list.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.2
 name:                hslua-list
-version:             1.1.3
+version:             1.1.4
 synopsis:            Opinionated, but extensible Lua list type.
 description:         List type for Lua, with a Haskell interface.
 homepage:            https://hslua.org/
@@ -58,7 +58,7 @@
   hs-source-dirs:      src
   c-sources:           cbits/listmod.c
   exposed-modules:     HsLua.List
-  build-depends:       bytestring
+  build-depends:       bytestring    >= 0.10.2 && < 0.13
 
 test-suite hslua-list-test
   import:              common-options
diff --git a/test/test-list.lua b/test/test-list.lua
--- a/test/test-list.lua
+++ b/test/test-list.lua
@@ -64,7 +64,12 @@
         local primes = List:new {2, 3, 5, 7}
         primes:extend {11, 13, 17}
         assert.are_same({2, 3, 5, 7, 11, 13, 17}, primes)
-      end)
+      end),
+
+      test('returns the extended table', function ()
+        local primes = List:new {2, 3, 5, 7}
+        assert.are_same(primes, primes:extend{11, 13, 17, 19})
+      end),
     },
 
     group 'filter' {
@@ -212,6 +217,42 @@
         count_norsk:insert(2, 'seks')
         assert.are_same({'fem', 'seks', 'syv'}, count_norsk)
       end)
+    },
+
+    group 'iter' {
+      test('is a function', function ()
+        assert.are_equal('function', type(List.iter))
+      end),
+      test('produces a function', function ()
+        assert.are_equal('function', type(List{1}:iter()))
+      end),
+      test('function steps through the list', function ()
+        local iterator = List{3,4}:iter()
+        assert.are_equal(3, iterator());
+        assert.are_equal(4, iterator());
+        assert.are_equal(nil, iterator());
+      end),
+      test('iterator produces `nil` for an empty list', function ()
+        assert.is_nil(List.iter({})())
+      end),
+      test('first argument is the step size', function ()
+        local iterator = List{3,4,5,6}:iter(2)
+        assert.are_equal(3, iterator());
+        assert.are_equal(5, iterator());
+        assert.are_equal(nil, iterator());
+      end),
+      test('starts from the end for negative step sizes', function ()
+        local iterator = List{3,4,5,6}:iter(-2)
+        assert.are_equal(6, iterator());
+        assert.are_equal(4, iterator());
+        assert.are_equal(nil, iterator());
+      end),
+      test('third argument is the initial index', function ()
+        local iterator = List{3,4,5,6}:iter(2, 2)
+        assert.are_equal(4, iterator());
+        assert.are_equal(6, iterator());
+        assert.are_equal(nil, iterator());
+      end),
     },
 
     group 'map' {
