CodeGym /Courses /C# SELF /Position-based merging with

Position-based merging with Zip

C# SELF
Level 33, Lesson 4
Available

1. Introduction

Imagine two stacks of letters: the first one has envelopes with names on them, the second one has postcards for those recipients. Your job is to match each envelope with the postcard from the same position in the second stack. First envelope with the first postcard, second with the second, and so on. You go through both stacks, grab one item from each, and "zip" them together.

In programming, this operation is called zip — just like a zipper that "connects" two sides, matching them strictly by position.

In LINQ, it's a handy and powerful tool for merging data from two (sometimes more) sequences, when order matters: first with first, second with second, and so on.
Important to remember: Zip only works as long as both collections have elements. If one is shorter — the result will be cut off at the shortest one.

Syntax and how it works

Zip takes two (or more) lists and merges them into a new one: elements are joined by their indexes, so 0th with 0th, 1st with 1st, etc. If one of the lists runs out first, new pairs won't be "zipped" anymore — the result will be as long as the shortest list.

Signature (main one):


IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
    this IEnumerable<TFirst> first,
    IEnumerable<TSecond> second,
    Func<TFirst, TSecond, TResult> resultSelector
)

Notice: Zip doesn't just return pairs, but whatever you specify in resultSelector. It could be a tuple, a string, an object, whatever — it's all up to your logic.

  • first and second — the collections to merge.
  • resultSelector — a function that takes the current elements from both collections and returns a new result.

In plain English: we walk through both collections at the same time, and at each step, we make whatever we need from their pairs.

2. Examples of using Zip — from simple to advanced

The simplest example: adding two arrays of numbers

Let's say we have two lists:

  • Math scores
  • Literature scores

We need to get the total score for each student at the same position.


// Two arrays with students' scores
int[] mathScores = { 5, 4, 3, 5, 2 };
int[] literatureScores = { 4, 5, 3, 4, 3 };

// Zip by position and add up
var totalScores = mathScores.Zip(literatureScores, (math, literature) => math + literature);

foreach (var score in totalScores)
    Console.WriteLine($"Total student score: {score}");

Total student score: 9
Total student score: 9
Total student score: 6
Total student score: 9
Total student score: 5

As you can see, it's super logical: the first student gets the sum of the first scores, the second gets the second, and so on.

Combining strings and numbers: labeling products with prices

Say you have a list of products and a list of prices. You need to print: "ProductName — Price".


string[] productNames = { "Apple", "Pear", "Banana" };
decimal[] productPrices = { 50.5m, 60.0m, 35.2m };

var info = productNames.Zip(productPrices, (name, price) => $"{name} — {price} euro");

foreach (var s in info)
    Console.WriteLine(s);

Apple — 50.5 euro
Pear — 60.0 euro
Banana — 35.2 euro

Using with collections of objects

Let's say in previous examples we had a Student class and a collection of students. Now — a separate array of ratings, matched by order:


public class Student
{
    public string Name { get; set; }
}

List<Student> students = new List<Student>
{
    new Student { Name = "Alice" },
    new Student { Name = "Bob" },
    new Student { Name = "Charlie" }
};

int[] ratings = { 7, 9, 8 };

var studentsWithRatings = students.Zip(ratings, (student, rating) =>
    $"{student.Name}: rating {rating}");

foreach (var s in studentsWithRatings)
    Console.WriteLine(s);

Alice: rating 7
Bob: rating 9
Charlie: rating 8

Merging more complex objects

In real life, you often need to merge, for example, two lists of orders from different systems: match each order with its processing result or status.


string[] orders = { "A-1", "B-2", "C-3" };
string[] statuses = { "Completed", "In progress", "Rejected" };

var orderStatusList = orders.Zip(statuses, (order, status) => new { Order = order, Status = status });

foreach (var os in orderStatusList)
    Console.WriteLine($"Order {os.Order}: {os.Status}");

Order A-1: Completed
Order B-2: In progress
Order C-3: Rejected

4. Important features and common mistakes

When you use Zip, remember: the resulting collection will be as long as the shortest input.
For example, if you have 10 students but only 7 grades, the result will have only 7 elements. This is a common reason for unexpected "loss" of data.

Mistake #1: mismatched lengths or order of collections.
If you accidentally mix up the order of lists or forget to sort them so that elements match, the resulting pair will be wrong.
Imagine: the student list doesn't match the order of grades — Alice might get Bob's grade. It's like wearing socks from different pairs: each is on your foot, but it just feels weird.

Mistake #2: one of the collections is empty.
If even one list is empty, the result will be empty.
Zip only works as long as there are elements in both sequences. If one source has no data, you get "nothing".

5. Zip with multiple collections

Originally, LINQ only supported merging two collections. But starting with .NET 6, there's an overload that lets you zip THREE (whoa!) collections at once.

Example with three collections:


string[] names = { "Rey", "Luke", "Leia" };
string[] planets = { "Tatooine", "Tatooine", "Alderaan" };
int[] ages = { 19, 23, 19 };

// In .NET 6+ there's an overload:
var characters = names.Zip(planets, ages, (name, planet, age) =>
    $"{name} ({planet}), {age} years old");

foreach (var c in characters)
    Console.WriteLine(c);

Rey (Tatooine), 19 years old
Luke (Tatooine), 23 years old
Leia (Alderaan), 19 years old

The number of elements in the result — by the shortest list! If ages is one less, the last Luke goes "ageless".

6. Zip and LINQ Query Syntax: does it exist?

You might have noticed that we're always using "method" syntax. In LINQ Query Syntax, there's no separate word for Zip, and you can't do it with from ... in .... The reason is simple: the concept of "zipping" is pairwise merging by position, and LINQ query composition works through combos of join, select, etc., usually for "linking" by key, not by position.

Moral of the story: For Zip use only the dot (well, method) syntax:


var zipped = collection1.Zip(collection2, (a, b) => ...);

If you really want to use query syntax — better not. But if you really, really want to, you can reinvent the wheel and write your own extension method. 🙂

7. Using Zip in real apps

Example from your practice: comparing old and new prices

Let's say in our learning app there's a list of products and every now and then new prices come in. You need to show how the price of each product changed:


string[] productNames = { "Apple", "Banana", "Pineapple" };
decimal[] oldPrices = { 80.0m, 30.0m, 110.0m };
decimal[] newPrices = { 75.0m, 33.0m, 120.0m };

var priceChanges = productNames
    .Zip(oldPrices, newPrices, (name, oldPrice, newPrice) => 
        $"{name}: was {oldPrice}, now {newPrice}, change: {newPrice - oldPrice:+#;-#;0}");

foreach (var s in priceChanges)
    Console.WriteLine(s);

Apple: was 80, now 75, change: -5
Banana: was 30, now 33, change: +3
Pineapple: was 110, now 120, change: +10

Practice: merging results from different sources

In "real" projects, Zip is often used to merge results from different APIs or tables, where the order of data is guaranteed externally (like arrays with weather forecasts and actual measurements).

8. Zip in LINQ chains: combining with other operators

Often Zip is used not by itself, but as part of long LINQ chains. For example, you can filter lists first, then zip them, and then — calculate some final stats.


var passedMath = mathScores.Where(x => x >= 3);
var passedLit = literatureScores.Where(x => x >= 3);

var passedPairs = passedMath.Zip(passedLit, (m, l) => m + l);
var avgScore = passedPairs.Average();

Console.WriteLine($"Average total score among those who passed: {avgScore}");

Pro tip: if after Where one of the lists got shorter, Zip will trim the result to the shortest one.

Comparing Zip and other merging methods

Method Essence Joins by… Result length…
Zip
Joins elements by index Index (position) Shortest collection
Join
Joins by key Common key All matching pairs
GroupJoin
For each group key — a collection Key By first collection
Concat
Sticks collections together one after another Just at the end Sum of lengths

Zip is about positional matching. Join is about matching by some common value (like a code or id).

9. Practical tips and common mistakes

When you use Zip in your app, the main thing is to make sure that:

  • The order of elements in both collections matches: otherwise you'll "glue" Alice and Bob's grades together.
  • Collections are properly sorted or prepped for synchronous iteration.
  • The result will always be as long as the shortest collection.
  • Don't use Zip if you need to match data by some key! That's what Join is for.
  • Another common mistake — you forgot that one collection has duplicates and the other doesn't. Or you changed the order during filtering and lost the match.
2
Task
C# SELF, level 33, lesson 4
Locked
List of products with prices
List of products with prices
1
Survey/quiz
Combining Collections, level 33, lesson 4
Unavailable
Combining Collections
Advanced LINQ: joins and projections
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION