rubyguides

Enumerable#uniq

The uniq method returns a new array containing only the unique elements from a collection. Duplicates are removed based on equality (==), and the original order of elements is preserved — the first occurrence of each element is kept.

Basic Usage

Remove duplicates from an array of numbers:

numbers = [1, 2, 2, 3, 1, 4, 3, 5]
numbers.uniq
# => [1, 2, 3, 4, 5]

The returned array contains one occurrence of each unique value, in the order they first appeared.

With strings:

uniq works on any collection whose elements respond to == and eql?, so strings are handled the same way as numbers. Ruby compares each element against the internal hash of already-seen values, keeping the first occurrence and discarding repeats. The original array order determines which duplicate wins — the element that appears earlier always takes priority.

languages = ["ruby", "python", "ruby", "go", "python", "rust"]
languages.uniq
# => ["ruby", "python", "go", "rust"]

With a Block

Pass a block to determine uniqueness based on the block’s return value instead of the elements themselves:

When you need uniqueness defined by something other than element identity — a computed attribute, a derived value, or a normalised form — pass a block. The block runs for each element and uniq compares the block results instead of the elements themselves. Only the first element that produces a given block result is kept; later elements that produce the same result are dropped.

words = ["apple", "apricot", "banana", "blueberry", "cherry"]
words.uniq { |word| word[0] }
# => ["apple", "banana", "cherry"]

The block evaluates word[0] for each element — "apple"[0] and "apricot"[0] both return "a", so only the first "a"-word survives. This is useful when you want uniqueness based on a specific attribute.

Practical block example: unique by attribute

Given a collection of objects, get one record per category:

products = [
  { name: "Laptop", category: "electronics" },
  { name: "Mouse", category: "electronics" },
  { name: "Carrot", category: "food" },
  { name: "Apple", category: "food" },
  { name: "Desk", category: "furniture" }
]

products.uniq { |p| p[:category] }
# => [{:name=>"Laptop", :category=>"electronics"},
#     {:name=>"Carrot", :category=>"food"},
#     {:name=>"Desk", :category=>"furniture"}]

Order Preservation

uniq preserves the original order of elements. The first occurrence of each unique element is kept:

The first-occurrence-wins rule means uniq is stable with respect to the original order. You can count on the result array preserving the sequence of the source collection — element 1 always appears before element 2 if they were in that order originally. This stability makes uniq safe to use in pipelines where the order carries meaning, such as ranked lists or chronological sequences.

[3, 1, 4, 1, 5, 9, 2, 6].uniq
# => [3, 1, 4, 5, 9, 2, 6]

This matters when order matters to your application — unlike some deduplication approaches that sort or randomize results.

Difference from & (set intersection)

Ruby has two ways to find unique or common elements. uniq removes duplicates from a single array, while & finds the intersection of two arrays:

a = [1, 2, 2, 3, 4]
b = [2, 3, 3, 5]

# uniq removes duplicates from ONE array
a.uniq
# => [1, 2, 3, 4]

# & finds elements present in BOTH arrays
a & b
# => [2, 3]

& requires two arrays and returns elements that exist in both. uniq operates on a single array and removes repeated elements, which makes it more straightforward when you only have one collection.

When to use each

Use uniq to deduplicate a single list. Use & to find common elements between two lists

# Deduplicate one list
["cat", "dog", "cat", "mouse"].uniq
# => ["cat", "dog", "mouse"]

# Find overlapping tags
user_tags = ["ruby", "rails", "postgres", "linux"]
post_tags = ["ruby", "python", "linux", "docker"]
user_tags & post_tags
# => ["ruby", "linux"]

Handling nil Values

nil is treated as a regular value and participates in uniqueness checking:

nil values participate in uniqueness checking just like any other value — two nil entries are considered equal, so only the first one survives. This behaviour is consistent with how Ruby hashes treat nil keys and avoids the surprise of nil being silently dropped from the output. If you need to strip nil values entirely, chain compact before uniq instead.

[1, nil, 2, nil, 3, nil].uniq
# => [1, nil, 2, 3]

One nil is kept along with one occurrence of each non-nil unique value.

With a block, nil block results work the same way:

When the block returns nil for certain elements, those elements are grouped together under the same nil result — and only the first one survives. This is useful when you want to collapse a category of values into a single representative. The combination of nil as a legitimate block return and the first-occurrence rule gives you a compact way to express “keep at most one element from this equivalence class.”

data = [1, 2, 3, 4, 5]
data.uniq { |n| n.even? ? "even" : nil }
# => [1, 2]

The block returns nil for odd numbers. Since all odd numbers produce the same block result (nil), only the first odd number survives.

Uniq on Hashes

When called on a hash, uniq first converts the hash to an array of [key, value] pairs:

stock = { apples: 50, bananas: 30, oranges: 25, apples: 20 }
stock.uniq
# => [[:apples, 50], [:bananas, 30], [:oranges, 25], [:apples, 20]]

Note that Ruby’s hash literal syntax actually prevents duplicate keys — the last value for :apples (20) overwrites the first (50). But when working with arrays of pairs or enumerators over hashes, uniq is useful:

Calling to_a on a hash produces an array of [key, value] pairs that matches what uniq already returns when called directly on a hash. The explicit to_a is mainly useful as documentation — it tells the reader that you are intentionally treating the hash as a list of pairs. The to_enum form can accept arguments if the method being enumerated requires them, but for uniq the two forms produce identical output.

# Often used with to_a or to_enum
stock.to_a.uniq
# => [[:apples, 50], [:bananas, 30], [:oranges, 25], [:apples, 20]]

# Using to_enum explicitly
stock.to_enum.uniq
# => [[:apples, 50], [:bananas, 30], [:oranges, 25], [:apples, 20]]

Practical Examples

Removing duplicate names

The practical examples that follow show uniq applied to common Ruby data shapes — string lists, arrays of hashes, and chained method pipelines. Each example highlights a different aspect of uniq: pure deduplication, block-based uniqueness by attribute, and combining uniq with counting or sorting for data analysis tasks.

attendees = ["Alice", "Bob", "Charlie", "Alice", "Diana", "Bob"]
attendees.uniq
# => ["Alice", "Bob", "Charlie", "Diana"]

Finding unique values by attribute

When the array elements are hashes with a shared key structure, the block form of uniq shines. You pass a block that extracts the attribute you want uniqueness on, and uniq keeps one hash per distinct attribute value. The result is still an array of the original hash elements — uniq does not flatten or transform the elements, it only decides which ones to keep.

users = [
  { name: "Alice", role: "admin" },
  { name: "Bob", role: "member" },
  { name: "Charlie", role: "admin" },
  { name: "Diana", role: "member" }
]

users.uniq { |u| u[:role] }
# => [{:name=>"Alice", :role=>"admin"},
#     {:name=>"Bob", :role=>"member"}]

Counting occurrences after uniq

Combine uniq with other methods to analyze data:

Chaining uniq with count is a quick way to answer “how many distinct values?” without manually building a set or tally. The combination urls.uniq.count reads naturally: first eliminate repeats, then count what remains. For more detailed frequency analysis, Enumerable#tally returns a hash mapping each value to its occurrence count, which is often more useful than just the distinct count.

urls = [
  "https://example.com/page1",
  "https://example.com/page2",
  "https://example.com/page1",
  "https://example.com/page3",
  "https://example.com/page2"
]

# How many unique pages were accessed?
urls.uniq.count
# => 3

# List the unique pages
urls.uniq
# => ["https://example.com/page1",
#     "https://example.com/page2",
#     "https://example.com/page3"]

Chaining with other enumerable methods

uniq sits comfortably in the middle of an Enumerable pipeline. You can filter with select, deduplicate with uniq, and sort with sort — each step producing a new array that flows into the next method. The order of operations matters: filtering before deduplication reduces the number of elements uniq must examine, while deduplication before sorting shrinks the list that sort must arrange.

scores = [100, 85, 100, 92, 85, 78, 100]

# Get unique passing scores, sorted
scores.select { |s| s >= 80 }.uniq.sort
# => [100, 85, 92, 78] → wait, let's trace this

scores.select { |s| s >= 80 }
# => [100, 85, 100, 92, 85, 100]

scores.select { |s| s >= 80 }.uniq
# => [100, 85, 92]

scores.select { |s| s >= 80 }.uniq.sort
# => [85, 92, 100]

Performance Notes

uniq iterates through the collection once and uses a hash internally to track seen elements. This gives it O(n) time complexity, where n is the collection size.

  • Best for: collections with many duplicates, or when you need block-based uniqueness
  • Alternative for small collections: if you only have two arrays and want common elements, & is more explicit
  • Does not modify the original: uniq returns a new array and leaves the original unchanged
original = [1, 2, 2, 3]
deduped = original.uniq

original
# => [1, 2, 2, 3]

deduped
# => [1, 2, 3]

Return Value

uniq always returns an array, even when called on an enumerable with no duplicates:

uniq always returns an array, even when the input has zero or one element. An empty array produces an empty result, and a single-element array produces an identical single-element array. This consistency means callers do not need to guard against nil or unexpected return types — the result is always iterable and responds to the full Array API.

[].uniq
# => []

[42].uniq
# => [42]

When given a block, the return type is still an array containing the original elements (not the block results):

Even when you pass a block, uniq returns the original elements — not the block results. The block only determines which elements are considered duplicates; it does not transform the output. If you want to both deduplicate and transform, chain uniq with map: first decide which elements to keep, then reshape them into the desired form. The separation of filtering from transformation keeps each step clear and testable independently.

[1, 2, 3].uniq { |n| n.even? }
# => [1, 2]

The block only determines uniqueness — the original elements are returned.

See Also