Array#uniq
The .uniq method removes duplicate elements from an array, keeping only the first occurrence of each unique value. The .uniq! variant modifies the array in place.
This is a good fit for cleanup tasks where order still matters. You get the first value you saw, not a reshuffled list, which keeps the result easy to compare with the original input.
numbers = [1, 1, 2, 2, 3, 3, 4, 4]
numbers.uniq
# => [1, 2, 3, 4]
# uniq! modifies in place
arr = [1, 1, 2, 2, 3]
arr.uniq!
arr # => [1, 2, 3]
That opening example shows the core behavior: the first copy of each value stays, and the rest disappear. It is a quick way to clean a list before you send it somewhere else or compare it with another array, and it works well when the order of the first values matters. The bang variant uniq! modifies the receiver in place instead of allocating a new array, which can be useful when memory is tight and you no longer need the original duplicates.
Syntax
array.uniq
array.uniq { |element| block }
array.uniq!
array.uniq! { |element| block }
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| block | Block | Optional | If provided, determines uniqueness based on the block’s return value |
what .uniq returns
.uniq returns a new array containing unique elements. The original array remains unchanged.
original = [1, 2, 2, 3, 3, 3]
unique = original.uniq
original # => [1, 2, 2, 3, 3, 3] — unchanged
unique # => [1, 2, 3] — new array
Those two return shapes make the methods easy to choose between. Use uniq when you want a fresh result and uniq! when you want to update the original array in place and you are fine with a possible nil return. The distinction is small, but it matters when the caller still needs the original array later, such as when another method will inspect the same array after cleanup.
.uniq! returns self if duplicates were removed, or nil if the array already had no duplicates:
arr = [1, 2, 3]
result = arr.uniq!
# => nil — no changes made
arr = [1, 1, 2]
result = arr.uniq!
# => [1, 2] — returns self
Those two return shapes make the methods easy to choose between. Use uniq when you want a fresh result and uniq! when you want to update the original array in place and you are fine with a possible nil return. The distinction is small, but it matters when the caller still needs the original array later, such as when another method will inspect the same array after cleanup.
In practice, that means uniq is the safer default when you want to preserve the source array, while uniq! is better when the caller already owns the array and expects the cleanup to happen in place. The distinction between the two forms follows Ruby’s convention of method names ending with ! signaling mutation, which makes the choice easy to remember once you are familiar with the pattern.
Using a Block
Pass a block to determine uniqueness based on a computed value:
# Get unique lengths
words = ["cat", "dog", "horse", "mouse", "cow"]
words.uniq { |word| word.length }
# => ["cat", "horse", "mouse"]
# First "cat" (len 3), then "horse" (len 5), then "mouse" (len 5 - duplicate!)
# Result: ["cat", "horse", "mouse"]
This is useful when you want uniqueness based on a specific attribute rather than the element itself. The block form is helpful when the raw elements are not the real comparison point. For example, two records may differ in content but still count as duplicates if they share the same length, type, or category. That idea also helps when you want to keep a stable order but compare by a derived value without changing the array structure around the values you keep. In other words, the block gives you a quick way to define what counts as the same item without giving up the predictable order of the first match.
The block form is helpful when the raw elements are not the real comparison point. For example, two records may differ in content but still count as duplicates if they share the same length, type, or category.
That idea also helps when you want to keep a stable order but compare by a derived value. You can group by length, first letter, status, or any other simple rule without changing the array structure around the values you keep.
In other words, the block gives you a quick way to define what counts as the same item without giving up the predictable order of the first match.
When the block is doing the comparison, it helps to keep the rule easy to explain in one sentence. If the filter becomes too clever, the reader has to mentally evaluate the method twice, which defeats part of the point of uniq.
common use cases
removing duplicates from user input
# Form submissions often have duplicate values
emails = ["user@example.com", "admin@example.com", "user@example.com"]
emails.uniq
# => ["user@example.com", "admin@example.com"]
Deduplicating email addresses is a pattern that comes up often in routine form processing scripts and CSV imports. When users submit the same address twice by accident, uniq cleans the list before you store or send the data downstream.
processing database results
# Database queries might return duplicate records
user_ids = [1, 2, 1, 3, 2, 1]
user_ids.uniq
# => [1, 2, 3]
Database queries sometimes return duplicate rows when joins produce multiple matches per record. Running uniq on the resulting ID list is a quick way to get a clean set before passing the IDs to another query or rendering them in a view.
with chaining
# Chain with other methods
[1, 1, 2, 2, 3, 3].select(&:odd?).uniq
# => [1, 3]
# Get unique values and sort
[3, 1, 2, 3, 1, 2].uniq.sort
# => [1, 2, 3]
This style works well because the cleanup step stays close to the rest of the transformation. You can read the chain in sequence and see exactly where the duplicate removal happens. That extra clarity is useful in review, because the reader can see the filter or sort that came first and the duplicate removal that came last. The result is a short pipeline that still explains itself without extra comments. When you chain uniq with other enumerable methods, the method usually sits near the end of the pipeline to clean up duplicates before the final result is returned.
That extra clarity is useful in review, because the reader can see the filter or sort that came first and the duplicate removal that came last. The result is a short pipeline that still explains itself without extra comments. When you chain uniq with other enumerable methods, the method usually sits near the end of the pipeline to clean up duplicates before the final result is returned.
When you chain uniq with other enumerable methods, the method usually sits near the end of the pipeline. That keeps the cleanup step obvious, and it makes the final array shape easier to reason about during review.
Gotchas
understanding first occurrence retention
.uniq always keeps the first occurrence, regardless of position:
# The second "cat" is removed, even though duplicates exist elsewhere
["cat", "dog", "cat", "bird", "cat"].uniq
# => ["cat", "dog", "bird"]
The first-occurrence rule is simple but important to remember when ordering matters. If you need the last occurrence instead, reverse the array before calling uniq and reverse it again afterward. That two-step pattern keeps the logic clear without adding a custom comparison block.
block returns nil
If the block returns nil, elements are compared by their nil status:
[1, 2, nil, nil, 3].uniq { |n| n&.even? }
# => [1, nil, 3]
# 1 -> even? returns false
# 2 -> even? returns true (kept)
# nil -> even? returns nil (different from false, kept)
# nil -> even? returns nil (same as previous nil, removed)
# 3 -> even? returns false (different from true, kept)
This is a good reminder that uniq compares the block result, not the original value, when you give it a block. A small difference like false versus nil can change which elements remain in the final array, so it helps to keep the block return values simple.
hash keys and uniq
Ruby hashes don’t allow duplicate keys, so converting through a hash can also remove duplicates (though order may vary in older Ruby):
# Alternative approach for uniqueness
[1, 2, 1, 3, 2].each_with_object({}) { |n, h| h[n] = true }.keys
# => [1, 2, 3]
# But prefer uniq for clarity and guaranteed order
The hash trick works, but uniq keeps the intent much clearer in ordinary code. It also avoids turning a simple order-preserving cleanup into a more indirect lookup step.
That keeps the example section balanced: you get the direct approach first, and then you can compare it against the more manual version when you need a fallback.
In everyday Ruby code, uniq is the clearest option because the intent is obvious at a glance and it reads well in pipelines.
That makes the method a good default for cleaning input, trimming query results, or preparing a list for display.