Desk Clarity

Everyday document & browser help

Itertools Groupby Splits the Same Key: Sort or Accumulate Deliberately

By ON Lab · Sources checked November 9, 2026

Groupby follows consecutive runs and shares an input iterator. Decide whether you need runs or all records per key, and materialize each group before advancing if it must survive.

Reproduce the boundary in a disposable example

Equal keys appear in several groups, or stored group iterators are empty later.

from itertools import groupby
records = [("b", 1), ("a", 2), ("b", 3)]
key = lambda row: row[0]
runs = [(k, list(g)) for k, g in groupby(records, key)]
assert [k for k, _ in runs] == ["b", "a", "b"]
ordered = sorted(records, key=key)
combined = [(k, list(g)) for k, g in groupby(ordered, key)]
assert combined == [("a", [("a", 2)]), ("b", [("b", 1), ("b", 3)])]
late = list(groupby(ordered, key))
assert [list(g) for _, g in late] == [[], []]
print("run grouping and immediate materialization verified")

Read the result before changing application code

The two b groups are correct for the original sequence’s runs. Sorting changes the input order to make all equal keys adjacent. The last assertion isolates a second mistake: collecting group iterator handles does not preserve their data after the outer iterator has consumed the source.

Choose the behavior your caller needs

For run-length encoding, keep original order and retain separate repeated-key groups. For all records per key, sort with the same key or accumulate into a dictionary of lists if preserving first-seen group order is useful. Materialize a group once and reuse that list for logging and processing. Consider memory: sorting or retaining every group is different from consuming a stream one group at a time.

Scope and limits

The example uses small in-memory tuples. It does not claim external sorting or an unbounded stream fits in memory. Unlike a database aggregate, this operation does not independently collect distant matching keys.

Related: compare the adjacent data or execution boundary.

The embedded example was executed in an isolated local Python process on November 9, 2026. It is a reproducible boundary check, not a test of your full application.

Sources