Enumerable#zip
The zip method combines elements from the caller with corresponding elements from one or more other enumerables. It returns a new array of arrays, where each inner array contains one element from each collection at the same index.
zip is defined on Enumerable, so it works on any collection that implements the enumerable protocol — arrays, hashes, ranges, and custom enumerables.
How it works
zip takes one or more collections as arguments. It iterates in parallel over the caller and all arguments, grouping elements at the same position into a sub-array:
collection.zip(*others)
The caller provides the elements for the first position in each inner array, and the arguments provide the remaining positions. The result is always an array of arrays, one per caller element.
Basic Usage
Combine two arrays element-by-element:
numbers = [1, 2, 3]
letters = ['a', 'b', 'c']
numbers.zip(letters)
# => [[1, "a"], [2, "b"], [3, "c"]]
The result has the same number of inner arrays as the caller’s length. Each inner array has one element from the caller plus one from each argument at the same index.
Handling different-length enumerables
When the collections have different lengths, zip stops at the shortest one. The extra elements from longer collections are discarded, and shorter ones get nil placeholders:
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]]
The order matters: whichever collection is the caller controls how many inner arrays appear in the result. When the caller is longer, extra positions are filled with nil.
To fill in a value other than nil, use cycle or pad the shorter array first:
a = [1, 2, 3]
b = ['a', 'b']
# Pad b with a placeholder
padded_b = b + [0] * (a.length - b.length)
a.zip(padded_b)
# => [[1, "a"], [2, "b"], [3, 0]]
Padding before zipping gives you full control over what fills missing spots. A sentinel value like 0 or :missing can make it clear which positions were originally absent.
With multiple enumerables
You can zip together more than two collections:
numbers = [1, 2, 3]
letters = ['a', 'b', 'c']
symbols = [:x, :y, :z]
numbers.zip(letters, symbols)
# => [[1, "a", :x], [2, "b", :y], [3, "c", :z]]
With more than two collections, each inner array grows to match the number of arguments. The result is still a flat list of arrays, which you can iterate over or transform further.
Converting to a Hash
A common pattern is turning two parallel arrays into a hash using Hash[]:
keys = [:name, :age, :city]
values = ['Alice', 30, 'Boston']
Hash[keys.zip(values)]
# => {:name => "Alice", :age => 30, :city => "Boston"}
Hash[] takes the array of two-element arrays and interprets each pair as a key and value. This is one of the most common zip patterns in Ruby code.
The to_h method on the result works equally well:
keys.zip(values).to_h
# => {:name => "Alice", :age => 30, :city => "Boston"}
Both approaches produce the same result, but to_h reads more naturally in a method chain. Choose whichever fits the surrounding code style.
Using with Hashes
When you zip a hash, only its values are used (keys are not treated as the enumerable content):
config = { theme: 'dark', font_size: 14 }
overrides = ['light', 16]
config.values.zip(overrides)
# => [["dark", "light"], [14, 16]]
Because a hash iterates as key-value pairs, calling zip directly on a hash would produce unexpected results. Always decide whether you want the keys, the values, or the pairs.
To zip hash keys with an array, call .keys or .to_a explicitly:
hash = { a: 1, b: 2, c: 3 }
keys = [:x, :y, :z]
hash.keys.zip(keys)
# => [[:a, :x], [:b, :y], [:c, :z]]
Zipping hash keys with another collection is a clean way to remap or rename keys. The result works directly with to_h for building a new hash with different key names.
With a Block
Pass a block to iterate over the zipped pairs without building an intermediate array:
[1, 2, 3].zip(['a', 'b', 'c']) do |num, letter|
puts "#{num} -> #{letter}"
end
# Output:
# 1 -> a
# 2 -> b
# 3 -> c
When called with a block, zip returns nil.
Relationship to Array#transpose
zip and Array#transpose both rearrange elements into nested arrays, but they work differently:
transposeoperates on an array of arrays and swaps rows and columns. It requires a rectangular structure.zippairs elements from multiple arrays at matching indices. It handles unequal lengths naturally.
# transpose swaps rows and columns
matrix = [[1, 2], ['a', 'b'], [:x, :y]]
matrix.transpose
# => [[1, "a", :x], [2, "b", :y]]
# zip combines arrays element-by-element
[1, 2, 3].zip(['a', 'b', 'c'])
# => [[1, "a"], [2, "b"], [3, "c"]]
For non-rectangular data, zip is more predictable since it stops at the shortest collection rather than raising an error.
Parameters
| Parameter | Type | Description |
|---|---|---|
*others | Enumerable | One or more collections to zip with self |
Return Value
Returns a new array of arrays. Each inner array contains elements at the same index from the caller and all arguments. Returns an empty array if the caller is empty.
See Also
- Enumerable#flat_map — transform and flatten in one step
- Enumerable#tally — count occurrences of elements