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… |
|---|---|---|---|
|
Joins elements by index | Index (position) | Shortest collection |
|
Joins by key | Common key | All matching pairs |
|
For each group key — a collection | Key | By first collection |
|
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.
GO TO FULL VERSION