Skip to content

Python Itertools — Complete Guide to Iterator Tools

DodaTech Updated 2026-06-29 6 min read

In this tutorial, you will learn about Python Itertools. We cover key concepts, practical examples, and best practices to help you master this topic.

The itertools module is a collection of tools for working with iterators efficiently. It's one of Python's hidden gems — once you learn it, you'll find yourself reaching for it constantly. Instead of writing nested loops and complex list comprehensions, itertools lets you compose Iterator operations that are memory-efficient and readable.

In this tutorial, you'll learn the most useful itertools functions and patterns. DodaTech uses itertools for processing large log files, generating test cases, and analyzing threat patterns without loading everything into memory.

What You'll Learn

  • Infinite iterators: count, cycle, repeat
  • Combining iterators: chain, zip_longest, product
  • Filtering: filterfalse, compress, takewhile, dropwhile
  • Grouping and selecting: groupby, islice, pairwise
  • Combinatorics: permutations, combinations, combinations_with_replacement
  • Memory-efficient processing with lazy evaluation

Why Itertools Matters

Python loops are fine for small data. When you're processing millions of log entries or generating complex combinations, itertools provides C-optimized functions that use minimal memory. Each function returns a lazy iterator — it produces values on demand rather than building a list in memory.

Infinite Iterators

from itertools import count, cycle, repeat

# count(start, step): like range() but infinite
for i in count(1):
    if i > 5:
        break
    print(i, end=" ")  # 1 2 3 4 5

# cycle(iterable): repeat forever
colors = cycle(["red", "green", "blue"])
for _ in range(6):
    print(next(colors), end=" ")  # red green blue red green blue

# repeat(element, times): same value N times
for x in repeat("hello", 3):
    print(x, end=" ")  # hello hello hello

Combining Iterators

from itertools import chain, zip_longest

# chain: concatenate multiple iterables
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
combined = list(chain(list1, list2, list3))
print(combined)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Instead of [1, 2, 3] + [4, 5, 6] + [7, 8, 9] which creates copies

# zip_longest: like zip but fills missing values
a = [1, 2, 3]
b = ["a", "b"]
print(list(zip(a, b)))           # [(1, 'a'), (2, 'b')]
print(list(zip_longest(a, b, fillvalue=None)))
# [(1, 'a'), (2, 'b'), (3, None)]

Filtering Iterators

from itertools import filterfalse, compress, takewhile, dropwhile

# filterfalse: opposite of filter (keeps False items)
numbers = [1, 2, 3, 4, 5, 6]
odds = list(filterfalse(lambda x: x % 2 == 0, numbers))
print(odds)  # [1, 3, 5]

# compress: pick items based on selectors
data = ["A", "B", "C", "D"]
selectors = [1, 0, 1, 0]
selected = list(compress(data, selectors))
print(selected)  # ['A', 'C']

# takewhile: take items while condition is True
print(list(takewhile(lambda x: x < 4, [1, 2, 3, 4, 5, 1])))
# [1, 2, 3]

# dropwhile: skip items while condition is True, then take rest
print(list(dropwhile(lambda x: x < 4, [1, 2, 3, 4, 5, 1])))
# [4, 5, 1]

Grouping with groupby

groupby is one of the most powerful itertools functions. It groups consecutive elements by a key function:

from itertools import groupby

# Simple grouping
data = ["apple", "avocado", "banana", "blueberry", "cherry"]
grouped = groupby(data, key=lambda x: x[0])
for letter, items in grouped:
    print(f"{letter}: {list(items)}")

# Expected:
# a: ['apple', 'avocado']
# b: ['banana', 'blueberry']
# c: ['cherry']

# IMPORTANT: groupby only groups CONSECUTIVE matching items!
items = [("A", 1), ("A", 2), ("B", 3), ("A", 4)]
# This would show: A: [1, 2], B: [3], A: [4]
# Not: A: [1, 2, 4]

# Sort first to group non-consecutive items
data.sort(key=lambda x: x[0])

Combinatorics

from itertools import permutations, combinations, combinations_with_replacement, product

items = ["A", "B", "C"]

# All orderings (order matters)
print(list(permutations(items, 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]

# All selections (order doesn't matter)
print(list(combinations(items, 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'C')]

# With replacement (can pick same item multiple times)
print(list(combinations_with_replacement(items, 2)))
# [('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]

# Cartesian product (all pairs from multiple sets)
print(list(product([1, 2], ["a", "b"])))
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]

Memory-Efficient File Processing

from itertools import islice, chain

def read_in_chunks(file_path, chunk_size=100):
    """Read a file in chunks without loading it all."""
    with open(file_path) as f:
        while True:
            chunk = list(islice(f, chunk_size))
            if not chunk:
                break
            yield chunk

# Process log file line by line in batches
def analyze_logs(file_path):
    alerts = []
    for chunk in read_in_chunks(file_path, 50):
        for line in chunk:
            if "ERROR" in line or "CRITICAL" in line:
                alerts.append(line.strip())
        if alerts:
            print(f"Found {len(alerts)} alerts in this chunk")
    return alerts

Real-World: Log File Analyzer

from itertools import groupby, filterfalse

def analyze_access_log(entries):
    """
    entries: list of (ip, status_code, path) tuples
    """
    # Group by IP
    entries.sort(key=lambda x: x[0])
    for ip, ip_entries in groupby(entries, key=lambda x: x[0]):
        ip_list = list(ip_entries)

        # Count errors per IP
        errors = list(filterfalse(lambda x: x[1] < 400, ip_list))
        if len(errors) > 5:
            print(f"ALERT: {ip} has {len(errors)} errors")

        # Count unique paths accessed
        paths = set(e[2] for e in ip_list)
        if len(paths) > 20:
            print(f"WARNING: {ip} accessed {len(paths)} different paths")

# Test
log_entries = [
    ("192.168.1.1", 200, "/index.html"),
    ("192.168.1.1", 404, "/admin.php"),
    ("192.168.1.1", 403, "/config.php"),
    ("192.168.1.1", 500, "/api/data"),
    ("10.0.0.1", 200, "/index.html"),
]
analyze_access_log(log_entries)

Common Pitfalls

Pitfall 1: Iterators are Exhausted After One Use

from itertools import count

counter = count(1)
print(next(counter))  # 1
print(list(zip(counter, ["a", "b", "c"])))  # [(2, 'a'), (3, 'b'), (4, 'c')]
print(list(zip(counter, ["x", "y"])))  # [(5, 'x'), (6, 'y')]
# counter continues where it left off!

# Convert to list if you need to reuse
counter = list(count(1, 5))  # Problem: this is infinite!

Pitfall 2: groupby Requires Sorted Data

# WRONG: groupby on unsorted data
data = ["banana", "apple", "avocado", "blueberry"]
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, list(group))
# b: ['banana'], a: ['apple', 'avocado'], b: ['blueberry']
# Same letter appears in two groups!

# RIGHT: sort first
data.sort()
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, list(group))
# a: ['apple', 'avocado'], b: ['banana', 'blueberry']

Pitfall 3: Forgetting itertools Functions Return Lazy Iterators

# chain returns an iterator, not a list
result = chain([1, 2], [3, 4])
print(result)  # <itertools.chain object at 0x...>
print(list(result))  # [1, 2, 3, 4]  # Must convert to list

Practice Questions

  1. Use chain to flatten a list of lists: [[1,2], [3,4], [5,6]] → [1,2,3,4,5,6].

  2. Use groupby to find consecutive duplicate values in a list: [1,1,2,2,2,3,1,1] → groups.

  3. Use permutations to generate all possible 4-digit PIN codes.

  4. Use product to generate all possible combinations of (color, size, material) from three lists.

  5. Use filterfalse and islice to get the first 10 non-empty lines from a file, ignoring comments.

Challenge: Password Cracking Simulator

Write a function that generates password candidates for dictionary-based attacks. Given a base word, generate all variations by:

  • Capitalizing different letters (product of uppercase/lowercase)
  • Appending common suffixes (!, 123, 2026, etc.)
  • Replacing letters with numbers (e -> 3, l -> 1, o -> 0, a -> @) Use product() and permutations() to generate all combinations. DodaTech's Security Testing tools use similar techniques for Penetration Testing.

Real-World Task: Log Rotation by Date

Write a function that reads a log file with timestamps and splits it into separate files by date. Use groupby to group consecutive same-date entries, and write each group to a YYYY-MM-DD.log file. This is how DodaTech's log management system organizes security audit logs for daily review.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro