list
python

How do I split a list into equally-sized chunks?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Splitting a list into chunks is a common task in Python when you need to batch API calls, process files in groups, or paginate work. The best solution depends on whether you want a list result, a lazy iterator, or strictly equal-sized groups with padding or validation.

The Basic Slicing Pattern

If you already have a list in memory, the simplest solution is slicing inside a comprehension. It is readable and works in every modern Python version.

python
1def chunk_list(items, size):
2    if size <= 0:
3        raise ValueError("size must be greater than 0")
4    return [items[i:i + size] for i in range(0, len(items), size)]
5
6data = [1, 2, 3, 4, 5, 6, 7]
7print(chunk_list(data, 3))

Output:

python
[[1, 2, 3], [4, 5, 6], [7]]

This is usually the right answer when:

  • the input is already a list
  • a shorter final chunk is acceptable
  • you want the result immediately

Use A Generator For Large Inputs

If the input is large, returning every chunk at once may waste memory. A generator yields one chunk at a time.

python
1def chunked(iterable, size):
2    if size <= 0:
3        raise ValueError("size must be greater than 0")
4
5    for i in range(0, len(iterable), size):
6        yield iterable[i:i + size]
7
8for chunk in chunked(list(range(10)), 4):
9    print(chunk)

This still relies on indexing, so it is best for sequences such as lists or tuples. If you want to support any iterable, use itertools.

itertools.batched In Modern Python

Python 3.12 added itertools.batched, which is the cleanest built-in option for general iterables. It yields tuples lazily, so it works well for streams and generators.

python
1from itertools import batched
2
3data = range(10)
4print(list(batched(data, 3)))

Output:

python
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)]

If you are on Python 3.13 or newer, batched also supports strict=True, which raises an error when the last chunk is incomplete. That is useful when "equally sized" must be literal rather than approximate.

python
1from itertools import batched
2
3for group in batched([1, 2, 3, 4, 5, 6], 2, strict=True):
4    print(group)

Padding To Force Equal Sizes

Sometimes you need every chunk to have exactly the same length, even if the final group is short. In that case, pad the remainder.

python
1from itertools import zip_longest
2
3def padded_chunks(items, size, fillvalue=None):
4    if size <= 0:
5        raise ValueError("size must be greater than 0")
6
7    iterator = iter(items)
8    return zip_longest(*[iterator] * size, fillvalue=fillvalue)
9
10print(list(padded_chunks([1, 2, 3, 4, 5], 2, fillvalue=0)))

Output:

python
[(1, 2), (3, 4), (5, 0)]

This pattern is useful when you are formatting tabular data or batching work for systems that require fixed-size blocks.

Equal Chunk Size Versus Equal Number Of Chunks

People often ask for "equally sized chunks" when they actually want "split this list into N chunks". Those are different problems.

Chunk size means:

  • choose the maximum size of each group
  • the last group may be smaller

Number of chunks means:

  • decide how many groups you want
  • distribute the elements across them

If you need a fixed number of groups, use a different approach:

python
1def split_into_n_chunks(items, n):
2    if n <= 0:
3        raise ValueError("n must be greater than 0")
4
5    k, m = divmod(len(items), n)
6    return [
7        items[i * k + min(i, m):(i + 1) * k + min(i + 1, m)]
8        for i in range(n)
9    ]
10
11print(split_into_n_chunks(list(range(10)), 3))

That produces groups that are as balanced as possible, which is not the same as fixed chunk size.

Common Pitfalls

  • Forgetting to validate the chunk size. A value of 0 should raise an error.
  • Assuming the last chunk will always be full. Most chunking functions allow a shorter final chunk.
  • Using list slicing on a general iterator. Slicing needs a sequence, while itertools.batched works on any iterable.
  • Confusing "size of each chunk" with "number of chunks to create".
  • Materializing every chunk in memory when a lazy iterator would be cheaper.

Summary

  • For a normal list, slicing with a comprehension is the simplest solution.
  • For large or streaming inputs, prefer a lazy approach.
  • 'itertools.batched is the modern built-in choice for general iterables.'
  • Use padding only when every chunk must have exactly the same length.
  • Be clear whether you want fixed chunk size or a fixed number of chunks.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.