collections.chunk
Split a list into consecutive batches of at most n items, in order; the last batch may be short.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 11 tests, run in TypeScript, Python and Rust.
What it does
Splits a list into consecutive batches of `size` items: the shape an API with a "100 records per request" limit, a bulk insert or a mail-merge run needs. Every item appears in exactly one batch, in its original order, and only the last batch may be short.
An empty list gives no batches (`[]`), not one empty batch (`[[]]`), so `for batch in chunk(items, 100)` never sends an empty request.
For example
chunk(1, 2, 3, 4, 5, 6, 7, 3)→ ×3 seven items in threes leaves a short last batchchunk(1, 2, 3, 4, 5, 6, 3)→ ×2 a size that divides exactly leaves no short batchchunk(a, b, c, 1)→ ×3 size one gives one batch per item
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
def chunk(items: Sequence[T], size: int) -> List[List[T]]
| items | T[] | the list to split; it is not modified |
| size | int | the most items per batch, 1 or greater |
| returns | T[][] | batches in input order; an empty list gives no batches |
Your code names it in one line, in the file that uses it
from fune.collections.chunk import chunk # collections.chunk@^1
from typing import Any, List, Sequence, TypeVar
T = TypeVar("T")
def _whole(value: Any) -> bool:
# bool is an int in Python; True would otherwise chunk by 1.
return isinstance(value, int) and not isinstance(value, bool)
def chunk(items: Sequence[T], size: int) -> List[List[T]]:
"""Split ``items`` into consecutive batches of at most ``size``, in order.
An empty list gives no batches rather than one empty batch, so a loop over
the result never sends an empty request. A size below 1 raises: it is
always a bug upstream, and "no limit" is the outage batching prevents.
"""
if isinstance(items, (str, bytes)) or not isinstance(items, (list, tuple)):
raise TypeError("chunk needs a list of items")
if not _whole(size):
raise TypeError("size must be a whole number, received %r" % (size,))
if size < 1:
raise ValueError("size must be at least 1, received %d" % (size,))
return [list(items[start : start + size]) for start in range(0, len(items), size)]Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add collections.chunk
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./collections.chunk-1.0.0-python.fune, or fetch it from a terminal with fune pull collections.chunk@1.0.0:python.
The whole function, every language, is one file too: collections.chunk-1.0.0.fune, 7,030 bytes, sha256 ac5a3b9d4cf08b46bee335755d23351d37b66124c4e76f71be2bf77ad356cf0e. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
# fune: before collections.chunk
after — your function gets the result and the arguments, and returns the final result.
# fune: after collections.chunk
replace — it requires no other capability, so there is no dependency to replace.
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show collections.chunk --steps.
# fune: step collections.chunk after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| seven items in threes leaves a short last batch | 1, 2, 3, 4, 5, 6, 7, 3 | → | ×3 |
| a size that divides exactly leaves no short batch | 1, 2, 3, 4, 5, 6, 3 | → | ×2 |
| size one gives one batch per item | a, b, c, 1 | → | ×3 |
| a size larger than the list is one batch holding everything | a, b, 10 | → | ×1 |
| a size equal to the list is one full batch | a, b, c, 3 | → | ×1 |
| an empty list gives no batches, not one empty batch | , 5 | → | |
| records pass through untouched and in order | items ×3, 2 | → | ×2 |
| one short of a whole number of batches | 1, 2, 3, 4, 5, 2 | → | ×3 |
| size zero is an error, not 'no limit' | 1, 2, 3, 0 | → | error: size must be at least 1 |
| a negative size is an error | 1, 2, 3, -2 | → | error: size must be at least 1 |
Show the other 1 test
| Case | Arguments | Expected | |
|---|---|---|---|
| a fractional size is an error | 1, 2, 3, 1.5 | → | error: size must be a whole number |
More from the author
A `size` below 1 is an error rather than "everything in one batch": a zero or negative batch size is always a bug upstream, and quietly sending the whole list at once is the outage the batching was meant to prevent. A fractional size is an error too (TypeScript and Python; Rust's `i64` cannot express one).
A `size` larger than the list gives one batch holding everything.
The batches are new lists; the input is never modified. The items themselves are not copied, so objects in the batches are the same objects as in the input.
Files
| Path | Bytes |
|---|---|
| README.md | 947 |
| impl/python.py | 1,026 |
| impl/rust.rs | 1,096 |
| impl/typescript.ts | 858 |
| vectors.json | 1,378 |