W3docs

Python Lists Group

Learn three ways to group Python lists: defaultdict, itertools.groupby, and dict comprehensions — with runnable examples and common pitfalls.

Grouping a list means partitioning its elements into sub-collections that share a common key — for example, grouping words by their first letter, or grouping records by a category field. Python offers three main approaches: a manual loop with collections.defaultdict, itertools.groupby from the standard library, and dict comprehensions. This chapter explains each technique, when to choose one over another, and the pitfalls to avoid.

Related chapters: Python Lists · List Methods · List Comprehension · Loop Lists · collections Module

What "grouping" means

Given a flat list and a key function that maps each element to a group label, the goal is to produce a mapping from each label to the list of elements that belong to it:

['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']
  key = first letter
  →  {'a': ['apple', 'avocado', 'apricot'],
      'b': ['banana', 'blueberry'],
      'c': ['cherry']}

The three techniques below all produce this kind of result. They differ in verbosity, performance, and the constraints they place on the input.

Technique 1: Manual loop with defaultdict

collections.defaultdict is the most common and flexible approach. When you access a key that does not yet exist, a defaultdict(list) automatically creates an empty list for that key, so you never need an if key in d guard.

from collections import defaultdict

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

by_letter = defaultdict(list)
for word in words:
    by_letter[word[0]].append(word)

for letter, group in sorted(by_letter.items()):
    print(f'{letter}: {group}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

Why use defaultdict instead of a plain dict?

With a plain dict you need an explicit check before the first append:

# Plain dict — more boilerplate, same result
by_letter = {}
for word in words:
    if word[0] not in by_letter:
        by_letter[word[0]] = []
    by_letter[word[0]].append(word)

A shorter alternative with a plain dict is dict.setdefault:

by_letter = {}
for word in words:
    by_letter.setdefault(word[0], []).append(word)

setdefault is fine for short scripts, but defaultdict is faster (no repeated key lookups) and more explicit about intent.

Grouping by a computed key

The key can be any expression, not just an attribute. Here, a list of integers is split into even and odd groups:

from collections import defaultdict

numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]

by_parity = defaultdict(list)
for n in numbers:
    by_parity['even' if n % 2 == 0 else 'odd'].append(n)

print('even:', sorted(by_parity['even']))  # even: [2, 4, 6]
print('odd:', sorted(by_parity['odd']))    # odd: [1, 1, 3, 3, 5, 5, 5, 9]

Grouping a list of dicts

This is the most common real-world scenario — grouping rows of data by a field value:

from collections import defaultdict

data = [
    {'category': 'fruit', 'name': 'apple'},
    {'category': 'vegetable', 'name': 'carrot'},
    {'category': 'fruit', 'name': 'banana'},
    {'category': 'vegetable', 'name': 'broccoli'},
]

grouped = defaultdict(list)
for item in data:
    grouped[item['category']].append(item['name'])

for category, names in grouped.items():
    print(f'{category}: {names}')
# fruit: ['apple', 'banana']
# vegetable: ['carrot', 'broccoli']

Technique 2: itertools.groupby

itertools.groupby groups consecutive elements that share the same key. It is useful when you need to preserve run-length structure or when the data is already sorted and you want to avoid building the whole dictionary at once (it is lazy/streaming).

from itertools import groupby

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

# groupby only groups consecutive elements, so sort first
words_sorted = sorted(words, key=lambda w: w[0])

for letter, group in groupby(words_sorted, key=lambda w: w[0]):
    print(f'{letter}: {list(group)}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

The critical gotcha: sort before groupby

groupby only groups consecutive items that share the same key. If the input is not sorted by the key, you get multiple small groups instead of one group per key:

from itertools import groupby

# Unsorted input — groupby produces WRONG results
numbers = [1, 1, 2, 3, 3, 1, 2, 2]
for key, group in groupby(numbers):
    print(f'{key}: {list(group)}')
# 1: [1, 1]   ← first run of 1s
# 2: [2]
# 3: [3, 3]
# 1: [1]      ← second run of 1s — NOT merged with the first!
# 2: [2, 2]

Always sort by the same key function before calling groupby:

numbers_sorted = sorted(numbers)
for key, group in groupby(numbers_sorted):
    print(f'{key}: {list(group)}')
# 1: [1, 1, 1]
# 2: [2, 2, 2]
# 3: [3, 3]

When groupby shines: streaming large data

Because groupby returns an iterator, it does not load all groups into memory at once. This makes it useful for processing large sorted files line by line without building a full dictionary.

from itertools import groupby

# Grouping namedtuple records
from collections import namedtuple

Product = namedtuple('Product', ['category', 'name', 'price'])
products = [
    Product('dairy', 'milk', 1.10),
    Product('fruit', 'apple', 1.20),
    Product('fruit', 'banana', 0.50),
    Product('vegetable', 'broccoli', 1.50),
    Product('vegetable', 'carrot', 0.80),
]
# products is already sorted by category here

for category, group in groupby(products, key=lambda p: p.category):
    items = list(group)
    print(f'{category}: {[p.name for p in items]}')
# dairy: ['milk']
# fruit: ['apple', 'banana']
# vegetable: ['broccoli', 'carrot']

Technique 3: Dict comprehension

A dict comprehension builds the grouped dict in one expression. It is concise but has a downside: the inner list comprehension re-scans the entire input for every unique key, making it O(n × k) where k is the number of unique keys. For small lists this is fine; for large lists, prefer defaultdict.

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

# Collect unique keys first, then build each group
letters = sorted(set(w[0] for w in words))
grouped = {letter: [w for w in words if w[0] == letter] for letter in letters}

for letter, group in grouped.items():
    print(f'{letter}: {group}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

This technique is most readable when the key set is small and already known — for example, grouping True/False results or a fixed set of categories.

Aggregating groups after grouping

A common follow-up to grouping is aggregation: computing a sum, average, minimum, or count per group. Combine defaultdict(list) with standard Python arithmetic:

from collections import defaultdict

scores = [
    ('Alice', 90), ('Bob', 75), ('Alice', 85),
    ('Bob', 88), ('Carol', 92),
]

by_student = defaultdict(list)
for name, score in scores:
    by_student[name].append(score)

for student, student_scores in sorted(by_student.items()):
    avg = sum(student_scores) / len(student_scores)
    print(f'{student}: scores={student_scores}, avg={avg:.1f}')
# Alice: scores=[90, 85], avg=87.5
# Bob: scores=[75, 88], avg=81.5
# Carol: scores=[92], avg=92.0

Choosing the right technique

SituationBest choice
General grouping, any orderdefaultdict(list)
Need to stream large sorted dataitertools.groupby
Small list, concise one-linerDict comprehension
Input is already sortedEither defaultdict or groupby
Need to aggregate (sum, avg, etc.)defaultdict(list) + arithmetic

Common pitfalls

Forgetting to sort before groupby. groupby only merges consecutive identical keys. Always sorted() the input by the same key function before passing it to groupby.

Assigning list(group) immediately. The group iterator from groupby is exhausted as soon as the outer for loop advances to the next key. Convert it to a list inside the loop body if you need to use it more than once.

Modifying the input list while grouping. Adding or removing elements from the list during a grouping loop produces unpredictable results. Build the grouped dict first, then modify elements.

defaultdict appearing in repr. defaultdict(list, {...}) looks different from a plain dict in repr. Wrap it with dict(grouped) when you need a plain dict output.

Practice

Practice
What must you do before passing a list to itertools.groupby() to get one group per unique key?
What must you do before passing a list to itertools.groupby() to get one group per unique key?
Was this page helpful?