Array#zip
What does zip do?
zip combines two or more arrays element-by-element, producing an array of tuples. Each tuple contains one element from each input array at the same index position.
Think of it as a zipper on a jacket — it pairs teeth from two sides in order.
Array#zip is defined on the Array class, but the implementation lives in Enumerable#zip. Array includes Enumerable, so Array inherits it. The behaviour is identical whether you call it on an Array or any other Enumerable.
Basic Usage
Combining two arrays
numbers = [1, 2, 3]
letters = ['a', 'b', 'c']
numbers.zip(letters)
# => [[1, "a"], [2, "b"], [3, "c"]]
With a single array argument
[10, 20, 30].zip([100, 200, 300])
# => [[10, 100], [20, 200], [30, 300]]
Without a block
Called without a block, zip returns a new array of arrays (an Enumerator if no arguments):
[1, 2, 3].zip # => #<Enumerator: [1, 2, 3]:zip>
[1, 2, 3].zip(['a', 'b', 'c']) # => [[1, "a"], [2, "b"], [3, "c"]]
Zipping Multiple Arrays
You can pass any number of arrays:
ids = [1, 2, 3]
names = ['Alice', 'Bob', 'Carol']
scores = [95, 87, 92]
ids.zip(names, scores)
# => [[1, "Alice", 95], [2, "Bob", 87], [3, "Carol", 92]]
Handling Different-Length Arrays
When arrays have different lengths, zip stops at the shortest one. Extra elements from longer arrays are discarded, and shorter arrays fill in with nil:
short = [1, 2]
long = ['a', 'b', 'c', 'd']
short.zip(long)
# => [[1, "a"], [2, "b"]]
# Reversing the order changes which elements survive
long.zip(short)
# => [["a", 1], ["b", 2], ["c", nil], ["d", nil]]
Filling short arrays with a default value
If you need a value other than nil, pad the shorter array first:
a = [1, 2, 3, 4]
b = ['x', 'y']
padded_b = b + [nil] * (a.length - b.length)
a.zip(padded_b)
# => [[1, "x"], [2, "y"], [3, nil], [4, nil]]
With a Block
Pass a block to iterate over each tuple without building the intermediate array:
names = ['Alice', 'Bob', 'Carol']
scores = [95, 87, 92]
names.zip(scores) do |name, score|
puts "#{name}: #{score}"
end
# Output:
# Alice: 95
# Bob: 87
# Carol: 92
# => nil
When called with a block, zip returns nil.
Practical Examples
Merging keys and values into a hash
The most common use case — pairing keys with values:
keys = [:name, :age, :city]
values = ['Alice', 30, 'Boston']
keys.zip(values).to_h
# => {:name => "Alice", :age => 30, :city => "Boston"}
Or equivalently, using Hash[]:
Hash[keys.zip(values)]
# => {:name => "Alice", :age => 30, :city => "Boston"}
Creating an indexed list
items = ['apple', 'banana', 'cherry']
indexes = [0, 1, 2]
items.zip(indexes).to_h
# => {"apple" => 0, "banana" => 1, "cherry" => 2}
Pairing data for iteration
When you have parallel arrays and want to process them together:
prices = [29.99, 49.99, 9.99]
names = ['book', 'shirt', 'pen']
prices.zip(names).each do |price, name|
puts "#{name} costs $#{price}"
end
# Output:
# book costs $29.99
# shirt costs $49.99
# pen costs $9.99
Interleaving two arrays
left = [1, 3, 5]
right = [2, 4, 6]
left.zip(right).flatten
# => [1, 2, 3, 4, 5, 6]
zip vs transpose
Both zip and transpose rearrange elements into nested arrays, but they work on different data structures:
| Method | Operates on | What it does |
|---|---|---|
zip | Multiple separate arrays | Pairs elements at the same index |
transpose | A single 2D array | Swaps rows and columns |
# zip pairs elements from separate arrays
[1, 2, 3].zip(['a', 'b', 'c'])
# => [[1, "a"], [2, "b"], [3, "c"]]
# transpose swaps rows and columns of a 2D array
matrix = [[1, 2], ['a', 'b'], [:x, :y]]
matrix.transpose
# => [[1, "a", :x], [2, "b", :y]]
Key difference: transpose requires a rectangular (consistent row length) structure and raises an error if rows differ. zip naturally handles unequal lengths by stopping at the shortest array.
Performance Notes
zip has O(n) time complexity where n is the length of the shortest array. It iterates once and creates a new array of n tuples.
Memory considerations
zipalways allocates a new array- The inner tuples are new array objects
- For large datasets, this allocation overhead adds up
When to use it
zip is ideal when you have parallel arrays that represent related data (keys/values, coordinates, name/score pairs). For quick iteration over parallel data, the block form avoids intermediate allocation:
# Block form: no intermediate array
[1, 2, 3].zip(['a', 'b', 'c']) { |n, l| puts "#{n}:#{l}" }
# No-block form: creates intermediate arrays
zipped = [1, 2, 3].zip(['a', 'b', 'c']) # => [[1, "a"], [2, "b"], [3, "c"]]
Converting to hash efficiently
When you need a hash, .to_h on the result is cleaner than Hash[] and has equivalent performance:
keys.zip(values).to_h # preferred in modern Ruby
Hash[keys.zip(values)] # older style, same result
See Also
- /reference/enumerable/enumerable-zip/ — The Enumerable version of zip (Array inherits this behaviour)
- /reference/enumerable/enumerable-map/ — Transform each element with a block
- /reference/array-methods/array-flatten/ — Flatten nested arrays
- /reference/enumerable/enumerable-reduce/ — Accumulate collection into a single value