Q&A
What is array.uniq in Ruby?
Overview
uniq is one of Ruby's most idiomatic array methods. It removes duplicates by comparing elements with eql? and hash - matching Ruby's equality semantics. The block form extends this to deduplicate by any derived value, making it powerful for working with collections of objects.
graph LR
A["[1,2,2,3,1].uniq"] --> B["[1,2,3] - new array, first-occurrence order"]
C["array.uniq!"] --> D["Modifies in place - returns nil if no change"]
E["array.uniq {|x| x.attr}"] --> F["Dedup by block value"]
style A fill:#1e1b4b,color:#fff
Basic Usage
arr = [1, 2, 2, 3, 1, 4, 3]
arr.uniq # => [1, 2, 3, 4] - new array, original unchanged
puts arr.inspect # [1, 2, 2, 3, 1, 4, 3] - unchanged!
uniq returns a new array; the original is untouched.
In-Place Variant: uniq!
arr = [1, 2, 2, 3]
arr.uniq! # => [1, 2, 3] - original modified
puts arr.inspect # [1, 2, 3]
arr2 = [1, 2, 3]
arr2.uniq! # => nil (no duplicates to remove - returns nil, not the array)
uniq! returns nil when no elements are removed - a common gotcha: don't assume the return value is always the array.
Block Form: Custom Uniqueness
Pass a block to define how elements are compared. The first element yielding each distinct block value is kept; subsequent elements yielding the same value are dropped:
# Deduplicate by the first character:
["apple", "avocado", "banana", "blueberry"].uniq { |s| s[0] }
# => ["apple", "banana"] - "avocado" dropped (a), "blueberry" dropped (b)
# Deduplicate objects by an attribute:
users = [
{id: 1, name: "Ada"},
{id: 2, name: "Linus"},
{id: 1, name: "Ada (duplicate)"},
]
users.uniq { |u| u[:id] }
# => [{id:1, name:"Ada"}, {id:2, name:"Linus"}] - first occurrence of id:1 kept
This is how you deduplicate an array of complex objects by a key without writing a manual loop.
How Uniqueness Is Determined
Without a block, uniq uses eql? and hash (Ruby's standard equality for hash/set membership):
[1, 1.0].uniq # => [1, 1.0] - Integer and Float are not eql?
["a", :a].uniq # => ["a", :a] - different types
Strings are compared by value:
["hello", "hello", "world"].uniq # => ["hello", "world"]
Related Methods
| Method | Purpose |
|---|---|
uniq / uniq! |
Remove duplicates |
uniq { } |
Remove duplicates by block value |
tally (Ruby 2.7+) |
Count occurrences: ["a","b","a"].tally # => {"a"=>2,"b"=>1} |
group_by |
Group elements by a key |
count |
Count occurrences of a value |
With Comparable Objects
For custom objects to be deduplicated correctly without a block, define eql? and hash:
class Point
attr_reader :x, :y
def initialize(x, y) = @x, @y = x, y
def eql?(other) = x == other.x && y == other.y
def hash = [x, y].hash
end
[Point.new(1,2), Point.new(1,2), Point.new(3,4)].uniq
# => [Point(1,2), Point(3,4)]
Without overriding these, each object is unique by identity (object_id).
Why This Matters in Interviews
Ruby interviews use uniq to test array manipulation and the block-form idiom. Key points: uniq returns a new array (doesn't mutate), uniq! mutates and returns nil when there's nothing to change (not the array), the block form deduplicates by a derived value (the standard way to deduplicate objects by attribute), and the underlying equality is eql?/hash. Showing the block form with objects signals real Ruby fluency beyond toy examples.
Common Pitfalls
- Expecting
uniqto modify the original (it doesn't - useuniq!). - Using the return value of
uniq!without checking fornil(it'snilwhen nothing changed). - Expecting
[1, 1.0].uniqto give[1]- they're noteql?, so both are kept. - Forgetting to define
eql?andhashfor custom objects, souniqnever deduplicates them. - Reaching for a manual loop when
uniq { |x| x.attr }is cleaner.
References
- Ruby
Array#uniq: https://docs.ruby-lang.org/en/master/Array.html#method-i-uniq