Question
How can I split a Python list of any length into smaller chunks of equal size?
For example, if I have a list with an arbitrary number of items, I want to divide it into sublists where each sublist contains n items. If the list length is not perfectly divisible by n, the final chunk may contain fewer items.
Related topics often include iterating over a list in chunks and applying similar chunking logic to strings.
Short Answer
By the end of this page, you will understand how list chunking works in Python, why slicing is the most common solution, how to handle uneven final chunks, and how this pattern is used in real programs such as batching API requests, pagination, and data processing.
Concept
In Python, chunking a list means breaking one list into smaller lists of a fixed size.
For example:
numbers = [1, 2, 3, 4, 5, 6, 7]
If the chunk size is 3, the result should be:
[[1, 2, 3], [4, 5, 6], [7]]
The last chunk contains the remaining items because the list length is not always a perfect multiple of the chunk size.
This matters because real programs often need to process data in batches instead of all at once. Common reasons include:
- sending API requests in groups
- paginating results
- processing large datasets safely
- reducing memory usage in some workflows
- grouping items for display in a user interface
The most common Python solution uses list slicing inside a loop or list comprehension. Slicing lets you take a portion of a list from one index to another.
A chunking solution usually follows this idea:
- start at index
0 - move forward by
chunk_sizeeach time - slice from the current index to
current index + chunk_size - stop when you reach the end of the list
This is simple, readable, and works well for beginners and production code.
Mental Model
Think of a list like a long loaf of bread.
- The full loaf is your original list.
- The knife cuts it every
nitems. - Each slice becomes one chunk.
- If the loaf does not divide evenly, the last piece is smaller.
So if your loaf has 10 items and you cut every 4 items, you get:
- first slice: 4 items
- second slice: 4 items
- last slice: 2 items
Python slicing is the knife that makes those cuts.
Syntax and Examples
The most common Python pattern is:
chunks = [my_list[i:i + size] for i in range(0, len(my_list), size)]
Example
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
size = 4
chunks = [numbers[i:i + size] for i in range(0, len(numbers), size)]
print(chunks)
Output:
[[1, 2, 3, 4], [5, 6, 7, 8], [9]]
How it works
range(0, len(numbers), size)generates starting indexes:0, 4, 8numbers[i:i + size]takes a slice from each starting index- each slice becomes one sublist in the result
Reusable function
def chunk_list(items, size):
return [items[i:i + size] for i in range(0, len(items), size)]
values = ['a', 'b', 'c', 'd', 'e']
print(chunk_list(values, 2))
Output:
[['a', 'b'], ['c', 'd'], ['e']]
Important rule
The chunk size should be a positive integer.
# Good
chunk_list([1, 2, 3], 2)
# Bad
chunk_list([1, 2, 3], 0)
A size of 0 will cause an error because range() cannot step by zero.
Step by Step Execution
Consider this code:
items = [10, 20, 30, 40, 50, 60, 70]
size = 3
chunks = [items[i:i + size] for i in range(0, len(items), size)]
print(chunks)
Step 1: Calculate the range
range(0, len(items), size)
Since len(items) is 7 and size is 3, this becomes:
range(0, 7, 3)
This produces the indexes:
0, 3, 6
Step 2: Slice at each index
For i = 0:
items[0:3] # [10, 20, 30]
For i = 3:
items[3:6] # [40, 50, 60]
For i = 6:
items[6:9] # [70]
Notice that Python slicing does not fail if the end index is beyond the list length. It simply stops at the end of the list.
Step 3: Collect all slices
The list comprehension builds:
[[10, 20, 30], [40, 50, 60], [70]]
Final output
[[10, 20, 30], [40, 50, 60], [70]]
Real World Use Cases
Chunking lists appears in many practical tasks.
Batch API requests
Some APIs limit how many items you can send at once.
user_ids = [1, 2, 3, 4, 5, 6, 7]
for batch in [user_ids[i:i + 3] for i in range(0, len(user_ids), 3)]:
print("Send batch:", batch)
Pagination
A web app may display records 20 at a time.
products = list(range(1, 51))
pages = [products[i:i + 20] for i in range(0, len(products), 20)]
Processing files in groups
If you need to process thousands of filenames, chunking can make the work easier to manage.
files = ["a.csv", "b.csv", "c.csv", "d.csv"]
chunks = [files[i:i + 2] for i in range(0, len(files), 2)]
Building UI rows
If a screen should show 3 cards per row, chunking can group the data:
cards = ["A", "B", "C", "D", "E"]
rows = [cards[i:i + 3] for i in range(0, len(cards), 3)]
Real Codebase Usage
In real codebases, developers usually wrap chunking logic in a function so it can be reused and validated.
Common helper function
def chunk_list(items, size):
if size <= 0:
raise ValueError("size must be greater than 0")
return [items[i:i + size] for i in range(0, len(items), size)]
This adds a guard clause to reject invalid input early.
Validation pattern
Developers often validate arguments before processing:
def send_batches(items, batch_size):
if batch_size <= 0:
raise ValueError("batch_size must be positive")
for i in range(0, len(items), batch_size):
batch = items[i:i + batch_size]
print("Processing", batch)
Streaming vs building a full list
In some projects, developers prefer a generator when the input is large:
def chunk_generator(items, size):
if size <= 0:
raise ValueError("size must be greater than 0")
for i in range(0, len(items), size):
yield items[i:i + size]
This yields one chunk at a time instead of creating the whole result list immediately.
Common patterns around chunking
- guard clauses for invalid chunk sizes
- early returns when the list is empty
- batch processing loops for APIs or database work
- configuration values such as
BATCH_SIZE = 100 - error handling when external systems reject oversized batches
Common Mistakes
Using a chunk size of 0
Broken code:
items = [1, 2, 3]
size = 0
chunks = [items[i:i + size] for i in range(0, len(items), size)]
This raises an error because range() cannot use a step of 0.
Avoid it by validating the size:
if size <= 0:
raise ValueError("size must be greater than 0")
Forgetting that the last chunk may be smaller
Broken assumption:
items = [1, 2, 3, 4, 5]
size = 2
chunks = [items[i:i + size] for i in range(0, len(items), size)]
for chunk in chunks:
print(chunk[1])
This fails on the last chunk if it has only one item.
Avoid it by checking length or designing your code to allow a shorter final chunk.
Using the wrong range step
Broken code:
items = [1, 2, 3, 4, 5, 6]
size = 2
chunks = [items[i:i + size] for i in range(0, len(items))]
This creates overlapping slices because the loop moves by 1, not by size.
Correct version:
chunks = [items[i:i + size] for i in range(0, len(items), size)]
Confusing flattening with chunking
Chunking creates nested lists:
[[1, 2], [3, 4], [5]]
It does not keep a flat list.
Expecting slicing to raise an error when the end index is too large
Python slicing is safe here:
items = [1, 2, 3]
print(items[2:10]) # [3]
This behavior is one reason chunking with slices works so well.
Comparisons
| Approach | Result type | Best for | Notes |
|---|---|---|---|
| List slicing with list comprehension | List of lists | Simple chunking | Most common and readable |
for loop with slicing | List of lists | Beginners, debugging | Easier to trace step by step |
Generator with yield | Iterator of chunks | Large inputs | More memory-friendly |
| Manual item-by-item grouping | Custom | Special rules | More code, useful for unusual logic |
List comprehension vs loop
# List comprehension
chunks = [items[i:i + size] for i in range(0, len(items), size)]
# For loop
chunks = []
for i in range(0, len(items), size):
chunks.append(items[i:i + size])
Both are correct. The loop version is often easier for beginners to understand.
List of chunks vs generator
def chunk_generator(items, size):
for i in range(0, len(items), size):
yield items[i:i + size]
Use a generator when:
- the input is large
- you want to process one chunk at a time
- you do not need all chunks stored at once
Cheat Sheet
Basic chunking
chunks = [items[i:i + size] for i in range(0, len(items), size)]
Reusable function
def chunk_list(items, size):
if size <= 0:
raise ValueError("size must be greater than 0")
return [items[i:i + size] for i in range(0, len(items), size)]
Generator version
def chunk_generator(items, size):
if size <= 0:
raise ValueError("size must be greater than 0")
for i in range(0, len(items), size):
yield items[i:i + size]
Key rules
sizemust be greater than0- the last chunk may be smaller
- slicing past the end of a list is safe in Python
- use
range(0, len(items), size)to jump by chunk size
Common pattern
for i in range(0, len(items), size):
chunk = items[i:i + size]
print(chunk)
Example
items = [1, 2, 3, 4, 5]
size = 2
# Result: [[1, 2], [3, 4], [5]]
FAQ
How do I split a Python list into chunks of size n?
Use slicing with a step in range():
[items[i:i + n] for i in range(0, len(items), n)]
What happens if the list length is not divisible by the chunk size?
The final chunk will contain the remaining items and may be smaller than the others.
Can I chunk a list without using a list comprehension?
Yes. You can use a regular for loop and append each slice to a result list.
Why does Python slicing work even when the end index is too large?
Python slices stop safely at the end of the list instead of raising an error.
Should I use a generator instead of returning a list?
Use a generator when you want to process chunks one at a time or reduce memory usage for large inputs.
Can I use the same idea for strings?
Yes. Strings also support slicing, so the same pattern works, but the result will be chunks of strings instead of lists.
What error happens if the chunk size is 0?
You will get an error from range() because its step cannot be zero.
Mini Project
Description
Build a small batching utility for processing email recipients. In real applications, you often cannot send thousands of records in one operation, so you split the data into smaller groups. This project demonstrates list chunking, validation, and iteration over each batch.
Goal
Create a Python function that splits a list of email addresses into fixed-size batches and prints each batch for processing.
Requirements
- Create a function that accepts a list of email addresses and a batch size.
- Validate that the batch size is greater than 0.
- Return the email addresses as a list of chunks.
- Print each chunk with its batch number.
- Allow the final batch to contain fewer items if needed.
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Add Rows to a Pandas DataFrame in Python
Learn how to add rows to a Pandas DataFrame, why repeated row appends are slow, and when to use loc, concat, or record lists.
Call a Function by Name in a Python Module
Learn how to call a function by name in a Python module using strings, getattr, and safe patterns for dynamic function dispatch.