itertools.pairwise: Compare Neighboring Values

Published on: August 31, 2026
Reading time: 4 minutes
Developer coding and analyzing data with Python

itertools.pairwise() walks through an iterable and yields pairs made from consecutive elements. Instead of managing indexes manually, you receive (previous, current) directly, which makes code easier to read and reduces boundary mistakes.

The function was added in Python 3.10 and is useful for time-series analysis, sequence validation, transition detection, geometry, logs, event processing, and many other tasks. It is lazy, so pairs are produced only when requested and the whole input does not need to be copied into memory.

How pairwise works

from itertools import pairwise

values = [10, 14, 13, 20]
for previous, current in pairwise(values):
    print(previous, current)

The output contains (10, 14), (14, 13), and (13, 20). An iterable with fewer than two items produces no pairs.

Calculate consecutive differences

temperatures = [21.2, 22.0, 21.7, 23.1]
differences = [b - a for a, b in pairwise(temperatures)]

This pattern appears in metrics, prices, sensor readings, and any data ordered over time. The code states the intention directly: compare each value with the next one.

Detect state transitions

states = ["offline", "offline", "online", "online", "error"]
for previous, current in pairwise(states):
    if previous != current:
        print(f"transition: {previous} -> {current}")

In observability systems, this approach identifies transitions without maintaining a separate variable for the previous state.

Validate increasing sequences

def strictly_increasing(values):
    return all(a < b for a, b in pairwise(values))

Combining pairwise with all() keeps evaluation lazy and stops immediately after the first violation. That can save considerable work for large streams.

Find gaps between dates

from datetime import date

dates = [date(2026, 1, 3), date(2026, 1, 8), date(2026, 1, 10)]
gaps = [(b - a).days for a, b in pairwise(dates)]

The same idea applies to timestamps, software versions, geographic positions, workflow steps, and checkpoints.

Build line segments

When a list represents points in a route, every consecutive pair defines a segment.

points = [(0, 0), (3, 4), (6, 4)]
segments = list(pairwise(points))

This is clearer than range(len(points) - 1) and avoids repeated index access.

Pairwise versus zip and slicing

pairs = zip(values, values[1:])

For small lists, zip with slicing works. However, the slice creates another list and the pattern does not apply directly to generators. Pairwise accepts any iterable and preserves lazy behavior.

Pairwise versus tee

Before Python 3.10, a common recipe used itertools.tee(). It remains useful for compatibility, but pairwise communicates the intention more clearly.

from itertools import tee

def compatible_pairwise(iterable):
    first, second = tee(iterable)
    next(second, None)
    return zip(first, second)

tee may buffer values internally when cloned iterators advance at different speeds. The standard pairwise implementation handles the required state for this specific case.

Use pairwise with generators

def readings():
    for value in range(1_000_000):
        yield value

for a, b in pairwise(readings()):
    if b - a != 1:
        break

No million-item list is created. This makes pairwise appropriate for files, network streams, database cursors, and long processing pipelines.

Detect missing identifiers

ids = [100, 101, 102, 106, 107]
for a, b in pairwise(ids):
    if b != a + 1:
        print("gap between", a, "and", b)

The pattern is useful for sequence numbers, batches, offsets, invoice ranges, and event logs.

Combine pairwise with enumerate

for index, (a, b) in enumerate(pairwise(values), start=1):
    print(index, a, b)

With a starting value of one, the index corresponds to the position of the second element in the original sequence. Document that convention when it matters.

Circular sequences

Pairwise does not connect the last item back to the first. For a cycle, append or chain the first item explicitly.

from itertools import chain

colors = ["red", "green", "blue"]
circular_pairs = pairwise(chain(colors, colors[:1]))

For a generator, capture only the first item and chain it at the end, taking care to handle an empty iterable.

Do not mutate the input while iterating

Changing a list while pairwise is consuming it can produce surprising results. Prefer creating a new sequence or applying transformations before building the pairs.

Handle missing data deliberately

Real-world series often contain None, NaN, or invalid records. Decide whether to filter, stop the segment, or keep the invalid pair for reporting.

data = [10, None, 12, 15]
valid = (x for x in data if x is not None)
print(list(pairwise(valid)))

Filtering changes adjacency: 10 and 12 become neighbors. That may be correct in one domain and misleading in another, so the policy should be explicit.

Performance characteristics

Pairwise has linear time complexity and uses little extra memory. Because it does not materialize every pair, it is normally efficient. The function applied to each pair may still dominate total execution time, especially if it performs I/O or expensive calculations.

Common mistakes

  • Expecting output from an iterable with one item.
  • Assuming the final item is connected to the first.
  • Consuming a generator before passing it to pairwise.
  • Ignoring missing values that alter adjacency semantics.
  • Using pairwise when the task needs windows of three or more items.

Larger sliding windows

Pairwise creates windows of exactly two elements. For larger sliding windows, use a deque-based recipe or aligned iterators. itertools.batched creates non-overlapping groups, which is a different operation and should not be confused with a sliding window.

Testing pairwise logic

Test empty input, one item, two items, repeated values, invalid values, and very large generators. Property-based tests are especially helpful for monotonicity and gap detection because they can generate many sequence shapes automatically.

Best practices

Confirm that data is sorted in the intended order, preserve laziness when possible, and use descriptive names such as previous and current. Continue learning with Academify guides on Python itertools, generators, list comprehensions, and Python functions.

Conclusion

itertools.pairwise is a small tool that solves a recurring problem elegantly. It removes manual indexing, accepts any iterable, and combines naturally with all, comprehensions, and lazy pipelines. Whenever you need to compare neighbors, detect transitions, or calculate deltas, pairwise should be one of the first options you consider.

External references

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python types.new_class: Dynamic Classes

    Learn Python types.new_class for dynamic classes with metaclasses, prepared namespaces, inheritance, metadata, and safe factories.

    Ler mais

    Tempo de leitura: 3 minutos
    30/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    partialmethod: Build Specialized Methods in Python

    Learn Python partialmethod for specialized methods with correct binding, fewer wrappers, clear domain names, and safe introspection.

    Ler mais

    Tempo de leitura: 2 minutos
    30/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    inspect.getmembers_static: Inspect Attributes Safely

    Use Python inspect.getmembers_static to list attributes without executing properties, descriptors, or unwanted dynamic lookup.

    Ler mais

    Tempo de leitura: 2 minutos
    30/08/2026
    Detailed close-up of yellow and white albino python scales, capturing texture and pattern.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    DynamicClassAttribute: Descriptors and Dynamic Access

    Understand Python DynamicClassAttribute, descriptors, class versus instance access, metaclasses, Enum behavior, and safe introspection.

    Ler mais

    Tempo de leitura: 2 minutos
    30/08/2026
    Close-up of vibrant JavaScript code featuring functions and syntax highlighting.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python UserList: Custom Sequences

    Learn Python UserList for custom mutable sequences with validation, normalization, mutation rules, copying, and predictable APIs.

    Ler mais

    Tempo de leitura: 2 minutos
    30/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python UserDict: Custom Mappings

    Learn Python UserDict for custom mappings with validation, normalization, composition, copying, and predictable mutation behavior.

    Ler mais

    Tempo de leitura: 2 minutos
    30/08/2026