Functional Weave
Code in Python

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 batch
  • chunk(1, 2, 3, 4, 5, 6, 3) → ×2 a size that divides exactly leaves no short batch
  • chunk(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]]
itemsT[]the list to split; it is not modified
sizeintthe most items per batch, 1 or greater
returnsT[][]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
impl/python.py · 25 lines · open · raw
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
Download for Python collections.chunk-1.0.0-python.fune · 4,980 bytes sha256 14b4ce35c73b0f9029a70dfa5e3c3aababe010c7c5e1c669eeec4ac8462f7fe6

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.

CaseArgumentsExpected
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
CaseArgumentsExpected
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

PathBytes
README.md947
impl/python.py1,026
impl/rust.rs1,096
impl/typescript.ts858
vectors.json1,378