IEnumerable
C#
Zip method
programming tutorial
.NET development

How to Zip one IEnumerable with itself

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Enumerable.Zip combines two sequences element by element, but behavior can be confusing when both inputs come from the same IEnumerable. The result depends on whether you want same-position pairing or adjacent-item pairing. Getting this right requires understanding deferred execution and how many times your source is enumerated.

What Happens with source.Zip(source, ...)

When you zip a sequence with itself directly, LINQ creates two enumerators that advance together. You get same-index pairs, not neighbors.

csharp
1using System;
2using System.Linq;
3
4var data = new[] { 10, 20, 30, 40 };
5
6var sameIndexPairs = data.Zip(data, (left, right) => (left, right));
7
8foreach (var pair in sameIndexPairs)
9{
10    Console.WriteLine($"{pair.left}, {pair.right}");
11}

Output:

text
110, 10
220, 20
330, 30
440, 40

This is valid when both sides are intentionally identical projections.

Adjacent Pairing with Skip(1)

For neighbor comparisons, zip the sequence with its shifted version.

csharp
1using System;
2using System.Linq;
3
4var data = new[] { 10, 20, 30, 40 };
5
6var neighbors = data.Zip(data.Skip(1), (current, next) => (current, next));
7
8foreach (var pair in neighbors)
9{
10    Console.WriteLine($"{pair.current} -> {pair.next}");
11}

Output:

text
10 -> 20
20 -> 30
30 -> 40

This pattern is ideal for deltas, trend detection, and windowed validation.

Compute Deltas from Adjacent Values

A practical use case is calculating incremental changes.

csharp
1using System;
2using System.Linq;
3
4var values = new[] { 3, 8, 6, 15, 18 };
5
6var deltas = values
7    .Zip(values.Skip(1), (a, b) => b - a)
8    .ToArray();
9
10Console.WriteLine(string.Join(", ", deltas));

Output:

text
5, -2, 9, 3

This is concise and avoids manual index loops.

Beware Deferred and Single-Use Enumerables

IEnumerable can represent many kinds of sources, including generators, file readers, and network-backed iterators. Re-enumerating these sources may be expensive or invalid.

To make zip behavior deterministic, materialize once when needed.

csharp
1using System.Collections.Generic;
2using System.Linq;
3
4public static class SequenceUtils
5{
6    public static IEnumerable<(T Current, T Next)> AdjacentPairs<T>(IEnumerable<T> source)
7    {
8        var buffer = source as T[] ?? source.ToArray();
9        return buffer.Zip(buffer.Skip(1), (a, b) => (a, b));
10    }
11}

Materialization trades memory for predictable behavior and avoids repeated external side effects.

Build a Reusable Extension Method

Wrapping the pattern in an extension improves readability and safety checks.

csharp
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5public static class EnumerableExtensions
6{
7    public static IEnumerable<(T Left, T Right)> Window2<T>(this IEnumerable<T> source)
8    {
9        if (source is null)
10            throw new ArgumentNullException(nameof(source));
11
12        var data = source as T[] ?? source.ToArray();
13        return data.Zip(data.Skip(1), (left, right) => (left, right));
14    }
15}

Usage:

csharp
1var steps = new[] { 2, 4, 7, 11 };
2foreach (var pair in steps.Window2())
3{
4    Console.WriteLine(pair);
5}

The extension hides complexity and encourages consistent usage across codebases.

Zip Length Rules and Edge Cases

Zip stops when the shorter sequence ends. With source and source.Skip(1), that means output length is n - 1.

Edge behavior:

  • empty sequence gives empty result
  • one-item sequence gives empty result
  • two-item sequence gives one pair

This behavior is often desirable, but test it when downstream logic assumes at least one pair.

Performance Notes

For in-memory arrays and lists, Zip plus Skip is efficient enough for most workloads. For hot loops over very large data, indexed for loops can be faster and allocate less. Start with readable LINQ, then optimize only when profiling proves a bottleneck.

If allocations matter, avoid repeated ToArray calls in nested pipelines.

Common Pitfalls

  • Expecting source.Zip(source, ...) to produce adjacent pairs instead of same-index pairs.
  • Re-enumerating single-use IEnumerable sources and getting inconsistent results.
  • Forgetting that Zip truncates to the shorter sequence.
  • Using LINQ pipelines in performance-critical loops without profiling.
  • Skipping null checks in reusable helper methods.

Summary

  • 'source.Zip(source, ...) pairs items with themselves at the same index.'
  • Use source.Zip(source.Skip(1), ...) for adjacent-item pairing.
  • Materialize deferred sources when deterministic multiple enumeration is required.
  • Encapsulate window pairing in extension methods for readability.
  • Confirm edge-case behavior because output length is typically n - 1.

Course illustration
Course illustration

All Rights Reserved.