rubyguides

Array#intersection

The intersection method returns a new array containing elements that are present in all given arrays. It performs a set-like intersection while preserving duplicate elements from the first array.

That makes it useful when you want the overlap between collections but still care about the original ordering from the left-hand side. It is a small method, but it saves you from writing a manual filter every time you need to compare lists.

Basic Usage

[1, 2, 3, 4].intersection([2, 4, 6])
# => [2, 4]

["a", "b", "c"].intersection(["b", "c", "d"])
# => ["b", "c"]

The basic examples show the shape of the result: only values that appear in both arrays are kept. Because the result follows the first array, you can usually read it as “keep what matches and keep the original order.”

With Duplicates

Unlike a pure set operation, intersection preserves the multiplicity of elements from the first array:

[1, 1, 2, 2, 3].intersection([1, 2, 3])
# => [1, 1, 2, 2, 3]

[1, 1, 1].intersection([1, 2])
# => [1, 1, 1]

This behavior is easy to miss if you think of intersection as a pure set operation. Ruby keeps the duplicate count from the first array, so the output can still reflect how often a value originally appeared.

Multiple Arrays

You can pass multiple arrays to intersect more than two at once:

[1, 2, 3].intersection([2, 3, 4], [3, 4, 5])
# => [3]

When more than two arrays are involved, the result becomes the shared subset across all of them. That is handy for permission checks, tag matching, or any case where several lists must agree on the same values.

The & Operator

The & operator is functionally identical to intersection. Use whichever reads better in your context:

[1, 2, 3] & [2, 3, 4]
# => [2, 3]

[1, 2, 3].intersection([2, 3, 4])
# => [2, 3]

Both produce the same result. intersection is more explicit, while & is shorter.

If the code is already full of set-style operations, intersection can be easier to scan because the name explains the intent directly. The operator form is nice when the expression is short and already readable.

Intersection vs select + include

You can achieve similar results using select and include?, but the performance characteristics differ significantly:

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

# Using intersection — O(n)
a.intersection(b)
# => [3, 4]

# Using select + include — O(n * m)
a.select { |x| b.include?(x) }
# => [3, 4]

intersection uses a hash-based lookup under the hood, making it O(n + m) rather than O(n * m). For large arrays, the difference is substantial.

That performance note matters most when the lists grow large or the check happens repeatedly. The method keeps the code short and usually makes the intent clearer than a hand-written nested lookup.

Practical Examples

finding common tags

article_tags = ["ruby", "rails", "performance", "testing"]
user_interests = ["ruby", "javascript", "performance"]

article_tags.intersection(user_interests)
# => ["ruby", "performance"]

This is a common pattern for content filtering, recommendation logic, or anything that needs the overlap between two sets of labels. The result is easy to pass into a later step because it is already deduplicated in the way the method defines.

When the overlap is the important part, intersection keeps the code focused on the shared values instead of the mechanics of comparing lists. That usually makes the later steps easier to read, especially when the intersection feeds into a permission check or a report.

Shared Permissions

required_permissions = ["read", "write", "delete"]
user_permissions = ["read", "write"]

access_granted = required_permissions.intersection(user_permissions).size == required_permissions.size
# => false

Here the intersection is a quick membership test across two lists. If every required permission survives the overlap, the user has enough access; if not, the missing value is easy to spot.

That pattern is especially handy when the list of required values is known ahead of time. You can compare once, inspect the overlap, and use the size of the result as a clear signal.

Overlapping Schedules

available_monday = ["alice", "bob", "carol"]
available_wednesday = ["bob", "carol", "dave"]
available_friday = ["carol", "dave", "eve"]

["alice", "bob", "carol"].intersection(available_monday, available_wednesday, available_friday)
# => ["carol"]

This last example shows why the method is useful for coordination problems. When several schedules need to agree, the intersection gives you the common slot without extra bookkeeping.

Performance Notes

  • intersection is optimized for set-like operations and is significantly faster than equivalent select + include? chains on large arrays
  • Duplicate elements from the first array are preserved in the result
  • Elements from subsequent arrays do not affect the count — only their presence matters
  • The operation is order-preserving from the first array

See Also