Ruby Array: The Complete Guide to Ruby Array Methods (2026)
By RubyLearning
Arrays are the workhorse data structure of Ruby. Whether you are processing CSV rows, building API responses, or managing queues, you will reach for an array dozens of times a day. This guide covers every essential ruby array method with clear examples you can paste straight into irb.
Unlike a quick reference card, this page walks through why each method exists, when to choose one over another, and the performance trade-offs that matter in production. Bookmark it and come back whenever you need a ruby arrays tutorial that goes beyond the basics.
Table of Contents
Creating Arrays
Ruby gives you several ways to create arrays. The one you choose depends on what data you already have and how readable you want the result to be.
Array Literals
Square brackets are the most common way to create a ruby array. You can mix types freely because Ruby arrays are heterogeneous.
numbers = [1, 2, 3, 4, 5]
mixed = [1, "two", :three, 4.0, nil]
empty = []
puts numbers.class # => Array
puts mixed.length # => 5
Array.new
Use Array.new when you know the size up front or need a default value. Be careful with mutable defaults — pass a block to avoid sharing a single object across all slots.
# Fixed size with default value
zeros = Array.new(5, 0)
# => [0, 0, 0, 0, 0]
# Block form (safe for mutable objects)
grid = Array.new(3) { Array.new(3, 0) }
# => [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
# DANGER: without a block, all rows share the same object
bad_grid = Array.new(3, Array.new(3, 0))
bad_grid[0][0] = 99
# bad_grid => [[99, 0, 0], [99, 0, 0], [99, 0, 0]] # all rows changed!
%w and %i Literals
%w creates an array of strings, and %i creates an array of symbols. Both split on whitespace, so there is no need for quotes or commas.
colors = %w[red green blue yellow]
# => ["red", "green", "blue", "yellow"]
methods = %i[get post put patch delete]
# => [:get, :post, :put, :patch, :delete]
# Interpolation variants: %W and %I
name = "ruby"
tags = %W[#{name} rails sinatra]
# => ["ruby", "rails", "sinatra"]
Kernel#Array Conversion
Array() is handy when you are not sure if a value is already an array. It wraps non-arrays and passes arrays through unchanged.
Array(nil) # => []
Array([1, 2, 3]) # => [1, 2, 3]
Array("hello") # => ["hello"]
Array(1..5) # => [1, 2, 3, 4, 5]
Accessing Elements
Ruby array indexing is zero-based. Negative indices count backward from the end, which is one of the language's most convenient features.
Basic Indexing
letters = %w[a b c d e]
letters[0] # => "a"
letters[2] # => "c"
letters[-1] # => "e" (last element)
letters[-2] # => "d" (second to last)
letters[10] # => nil (out of bounds returns nil)
Slicing
Pass a range or a start-and-length pair to extract sub-arrays.
letters = %w[a b c d e]
letters[1..3] # => ["b", "c", "d"]
letters[1...3] # => ["b", "c"] (exclusive end)
letters[2, 2] # => ["c", "d"] (start, length)
letters.slice(0, 3) # => ["a", "b", "c"]
first, last, and sample
nums = [10, 20, 30, 40, 50]
nums.first # => 10
nums.first(3) # => [10, 20, 30]
nums.last # => 50
nums.last(2) # => [40, 50]
nums.sample # => random element
nums.sample(2) # => 2 random elements (no duplicates)
dig (Safe Nested Access)
dig safely traverses nested arrays without raising NoMethodError on nil. It was introduced in Ruby 2.3 and is essential for working with complex data structures.
matrix = [[1, 2], [3, 4], [5, 6]]
matrix.dig(1, 0) # => 3
matrix.dig(5, 0) # => nil (no exception)
# Compare with direct access:
# matrix[5][0] # => NoMethodError: undefined method '[]' for nil
Adding Elements
Ruby provides multiple methods for adding elements to an array. The choice depends on where you want to insert and whether you are combining arrays.
push, <<, and append
All three add elements to the end. The shovel operator (<<) is idiomatic Ruby and the most commonly used.
stack = [1, 2, 3]
stack.push(4) # => [1, 2, 3, 4]
stack << 5 # => [1, 2, 3, 4, 5]
stack.append(6, 7) # => [1, 2, 3, 4, 5, 6, 7]
# push and append accept multiple arguments
stack.push(8, 9) # => [1, 2, 3, 4, 5, 6, 7, 8, 9]
unshift and prepend
Add elements to the front. prepend is an alias for unshift added in Ruby 2.5.
queue = [3, 4, 5]
queue.unshift(1, 2) # => [1, 2, 3, 4, 5]
queue.prepend(0) # => [0, 1, 2, 3, 4, 5]
insert
Insert one or more elements at any position. Existing elements shift to the right.
arr = %w[a b e f]
arr.insert(2, "c", "d")
# => ["a", "b", "c", "d", "e", "f"]
# Negative indices work too
arr.insert(-1, "g")
# => ["a", "b", "c", "d", "e", "f", "g"]
concat and +
concat mutates the receiver. The + operator returns a new array.
a = [1, 2]
b = [3, 4]
a.concat(b) # => [1, 2, 3, 4] (a is modified)
puts a # => [1, 2, 3, 4]
c = [10, 20]
d = c + [30] # => [10, 20, 30] (c is unchanged)
puts c # => [10, 20]
Removing Elements
pop and shift
pop removes from the end; shift removes from the front. Both return the removed element(s).
arr = [1, 2, 3, 4, 5]
arr.pop # => 5 arr is now [1, 2, 3, 4]
arr.pop(2) # => [3, 4] arr is now [1, 2]
arr.shift # => 1 arr is now [2]
delete and delete_at
delete removes all occurrences of a value. delete_at removes by index.
arr = [1, 2, 3, 2, 4, 2]
arr.delete(2) # => 2 arr is now [1, 3, 4]
arr.delete_at(0) # => 1 arr is now [3, 4]
arr.delete(99) # => nil (not found)
compact and uniq
compact strips nil values. uniq removes duplicates. Both have bang variants that mutate in place.
data = [1, nil, 2, nil, 3, 1, 2]
data.compact # => [1, 2, 3, 1, 2] (nils removed)
data.uniq # => [1, nil, 2, 3] (duplicates removed)
data.compact.uniq # => [1, 2, 3] (both)
# Bang versions modify the original
data.compact! # data is now [1, 2, 3, 1, 2]
data.uniq! # data is now [1, 2, 3]
reject and select (Non-Destructive Filtering)
While delete mutates, reject and select return new arrays, which is usually safer.
nums = [1, 2, 3, 4, 5, 6]
odds = nums.reject { |n| n.even? } # => [1, 3, 5]
evens = nums.select { |n| n.even? } # => [2, 4, 6]
# Original is unchanged
puts nums # => [1, 2, 3, 4, 5, 6]
Searching and Filtering
These ruby array methods help you find elements without mutating the original collection.
include?
colors = %w[red green blue]
colors.include?("red") # => true
colors.include?("purple") # => false
find and find_index
find (aliased as detect) returns the first element matching a block. find_index returns its position.
users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Carol", age: 35 }
]
users.find { |u| u[:age] > 28 }
# => { name: "Alice", age: 30 }
users.find_index { |u| u[:name] == "Bob" }
# => 1
select and reject
select (aliased as filter) keeps elements where the block returns truthy. reject keeps elements where the block returns falsy.
scores = [82, 55, 91, 47, 73, 68, 95]
passing = scores.select { |s| s >= 60 }
# => [82, 91, 73, 68, 95]
failing = scores.reject { |s| s >= 60 }
# => [55, 47]
grep
grep filters using the === operator, which makes it powerful with regular expressions, ranges, and classes.
items = ["apple", "banana", 42, "avocado", 3.14, nil]
items.grep(String) # => ["apple", "banana", "avocado"]
items.grep(/^a/) # => ["apple", "avocado"]
items.grep(Numeric) # => [42, 3.14]
items.grep(1..50) # => [42, 3.14]
any?, all?, none?, and count
nums = [2, 4, 6, 8]
nums.any?(&:odd?) # => false
nums.all?(&:even?) # => true
nums.none?(&:odd?) # => true
nums.count(&:even?) # => 4
# With no block, checks truthiness
[nil, false, nil].any? # => false
[1, nil, 3].any? # => true
Transforming Arrays
Transformation methods return new arrays (or other structures) derived from the original. These are the heart of functional-style Ruby programming.
map / collect
map and collect are identical. Most Rubyists prefer map. They return a new array with the block's result for each element.
names = %w[alice bob carol]
names.map(&:upcase)
# => ["ALICE", "BOB", "CAROL"]
names.map { |name| name.length }
# => [5, 3, 5]
# map! modifies in place
names.map!(&:capitalize)
# names is now ["Alice", "Bob", "Carol"]
flat_map
flat_map is equivalent to map followed by flatten(1). It is perfect when each element expands into an array.
sentences = ["hello world", "ruby arrays"]
sentences.flat_map { |s| s.split(" ") }
# => ["hello", "world", "ruby", "arrays"]
# Without flat_map you would get nested arrays:
sentences.map { |s| s.split(" ") }
# => [["hello", "world"], ["ruby", "arrays"]]
flatten
flatten recursively flattens nested arrays. Pass a depth argument to limit how many levels deep it goes.
nested = [1, [2, 3], [4, [5, 6]]]
nested.flatten # => [1, 2, 3, 4, 5, 6]
nested.flatten(1) # => [1, 2, 3, 4, [5, 6]]
zip
zip merges arrays element-by-element, creating pairs (or tuples).
keys = [:name, :age, :city]
values = ["Alice", 30, "Tokyo"]
keys.zip(values)
# => [[:name, "Alice"], [:age, 30], [:city, "Tokyo"]]
# Convert to a hash in one step
Hash[keys.zip(values)]
# => { name: "Alice", age: 30, city: "Tokyo" }
# Or use .to_h
keys.zip(values).to_h
# => { name: "Alice", age: 30, city: "Tokyo" }
transpose
transpose swaps rows and columns in a two-dimensional array. Every sub-array must have the same length.
table = [
["Name", "Age", "City"],
["Alice", 30, "Tokyo"],
["Bob", 25, "London"]
]
table.transpose
# => [["Name", "Alice", "Bob"],
# ["Age", 30, 25],
# ["City", "Tokyo", "London"]]
Sorting and Reordering
sort and sort_by
sort uses the <=> (spaceship) operator by default. sort_by is more efficient when computing a sort key is expensive, because it only evaluates the block once per element (Schwartzian transform).
words = %w[banana apple cherry date]
words.sort
# => ["apple", "banana", "cherry", "date"]
words.sort_by { |w| w.length }
# => ["date", "apple", "banana", "cherry"]
# Custom comparator
words.sort { |a, b| b.length <=> a.length }
# => ["banana", "cherry", "apple", "date"] (longest first)
reverse and shuffle
arr = [1, 2, 3, 4, 5]
arr.reverse # => [5, 4, 3, 2, 1]
arr.shuffle # => [3, 1, 5, 2, 4] (random order)
# Reproducible shuffle with a seed
arr.shuffle(random: Random.new(42))
min_by, max_by, and minmax_by
products = [
{ name: "Widget", price: 9.99 },
{ name: "Gadget", price: 24.50 },
{ name: "Gizmo", price: 4.75 }
]
products.min_by { |p| p[:price] }
# => { name: "Gizmo", price: 4.75 }
products.max_by { |p| p[:price] }
# => { name: "Gadget", price: 24.50 }
products.minmax_by { |p| p[:price] }
# => [{ name: "Gizmo", price: 4.75 }, { name: "Gadget", price: 24.50 }]
Iterating
Iteration is where Ruby's block syntax truly shines. These ruby array methods let you walk through elements in various ways.
each and each_with_index
fruits = %w[apple banana cherry]
fruits.each { |fruit| puts fruit }
# apple
# banana
# cherry
fruits.each_with_index do |fruit, i|
puts "#{i}: #{fruit}"
end
# 0: apple
# 1: banana
# 2: cherry
each_with_object
each_with_object passes an accumulator alongside each element. Unlike inject, you do not need to return the accumulator from the block.
words = %w[hello world ruby]
result = words.each_with_object({}) do |word, hash|
hash[word] = word.length
end
# => {"hello" => 5, "world" => 5, "ruby" => 4}
# Build arrays conditionally
evens = (1..10).each_with_object([]) do |n, arr|
arr << n if n.even?
end
# => [2, 4, 6, 8, 10]
each_slice and each_cons
each_slice breaks an array into chunks of a given size. each_cons produces overlapping (sliding window) groups.
nums = (1..9).to_a
nums.each_slice(3).to_a
# => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
nums.each_cons(3).to_a
# => [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6],
# [5, 6, 7], [6, 7, 8], [7, 8, 9]]
# Practical: batching API calls
user_ids = (1..100).to_a
user_ids.each_slice(20) do |batch|
# process 20 users at a time
end
Aggregating and Reducing
These methods collapse an array into a single value. They are indispensable for data processing workflows. Tools like IntelDaily.ai rely on similar aggregation patterns when processing large volumes of monitoring data from websites and social feeds.
sum, count, min, max
scores = [88, 92, 75, 91, 83]
scores.sum # => 429
scores.count # => 5
scores.min # => 75
scores.max # => 92
scores.minmax # => [75, 92]
# sum with an initial value
scores.sum(100) # => 529
# sum with a block (Ruby 2.4+)
words = %w[hello world]
words.sum(0) { |w| w.length } # => 10
reduce / inject
reduce (aliased as inject) is the most flexible aggregation method. It passes an accumulator and each element to the block.
# Sum (equivalent to .sum)
[1, 2, 3, 4, 5].reduce(0) { |sum, n| sum + n }
# => 15
# Shorthand with a symbol
[1, 2, 3, 4, 5].reduce(:+)
# => 15
# Build a frequency hash
words = %w[apple banana apple cherry banana apple]
freq = words.reduce(Hash.new(0)) do |counts, word|
counts[word] += 1
counts
end
# => {"apple" => 3, "banana" => 2, "cherry" => 1}
# Find the longest string
%w[cat elephant dog].reduce do |longest, word|
word.length > longest.length ? word : longest
end
# => "elephant"
tally (Ruby 2.7+)
tally counts occurrences of each element, replacing the common reduce pattern for frequency hashes.
votes = %w[yes no yes yes no abstain yes no]
votes.tally
# => {"yes" => 4, "no" => 3, "abstain" => 1}
# Equivalent to the reduce pattern above, but cleaner
Multi-Dimensional Arrays
Ruby does not have a native matrix type, but nested arrays work well for grids, tables, and coordinate systems.
Creating and Accessing
# 3x3 grid initialized with zeros
grid = Array.new(3) { Array.new(3, 0) }
# => [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
# Set and get values
grid[1][2] = 5
grid.dig(1, 2) # => 5
# Iterate over all cells
grid.each_with_index do |row, r|
row.each_with_index do |cell, c|
puts "grid[#{r}][#{c}] = #{cell}"
end
end
Flattening and Transposing
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Flatten to a single dimension
matrix.flatten
# => [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Transpose rows and columns
matrix.transpose
# => [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# Extract a column
matrix.map { |row| row[1] }
# => [2, 5, 8] (second column)
Array Destructuring
Ruby supports powerful array destructuring (also called multiple assignment or parallel assignment) that lets you unpack arrays into individual variables.
Basic Destructuring
a, b, c = [1, 2, 3]
# a => 1, b => 2, c => 3
# Fewer variables than elements (extras are discarded)
x, y = [10, 20, 30]
# x => 10, y => 20
# More variables than elements (extras become nil)
a, b, c = [1, 2]
# a => 1, b => 2, c => nil
Splat Operator
The splat (*) operator captures remaining elements into an array.
first, *rest = [1, 2, 3, 4, 5]
# first => 1, rest => [2, 3, 4, 5]
*init, last = [1, 2, 3, 4, 5]
# init => [1, 2, 3, 4], last => 5
first, *middle, last = [1, 2, 3, 4, 5]
# first => 1, middle => [2, 3, 4], last => 5
Nested Destructuring
# Destructure nested arrays with parentheses
data = [1, [2, 3], 4]
a, (b, c), d = data
# a => 1, b => 2, c => 3, d => 4
# Practical: iterate over pairs
coordinates = [[0, 0], [1, 5], [3, 2]]
coordinates.each do |(x, y)|
puts "x=#{x}, y=#{y}"
end
Swapping Variables
a = "hello"
b = "world"
a, b = b, a
# a => "world", b => "hello"
Performance Tips
Arrays are fast for most operations, but some patterns can hurt performance in tight loops or with large datasets.
Use Set for Membership Tests
Array#include? is O(n). If you are checking membership repeatedly, convert to a Set first for O(1) lookups.
require 'set'
allowed_ids = [101, 202, 303, 404, 505] # imagine thousands
allowed_set = allowed_ids.to_set
# Slow: O(n) per check
allowed_ids.include?(303)
# Fast: O(1) per check
allowed_set.include?(303)
Prefer each over map When You Discard Results
map allocates a new array for the results. If you only care about side effects (printing, writing to a database), use each.
# Bad: allocates an unused array
users.map { |u| send_email(u) }
# Good: no extra allocation
users.each { |u| send_email(u) }
Chain Lazily with .lazy
When chaining multiple transformations on large arrays, .lazy avoids creating intermediate arrays.
# Without lazy: creates 3 intermediate arrays
result = (1..1_000_000)
.select { |n| n.odd? }
.map { |n| n ** 2 }
.first(5)
# With lazy: processes elements one at a time, stops early
result = (1..1_000_000)
.lazy
.select { |n| n.odd? }
.map { |n| n ** 2 }
.first(5)
# => [1, 9, 25, 49, 81]
Freeze Constant Arrays
Freeze arrays that should not change to prevent accidental mutation and enable potential runtime optimizations.
VALID_STATUSES = %w[pending active suspended].freeze
VALID_STATUSES << "deleted"
# => FrozenError: can't modify frozen Array
unshift vs push Performance
push (append) is amortized O(1). unshift (prepend) is O(n) because it must shift all existing elements. If you need a FIFO queue, consider Queue from the standard library or build one from two stacks.
Common Patterns and Idioms
Grouping with group_by
users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "user" },
{ name: "Carol", role: "admin" },
{ name: "Dave", role: "user" }
]
users.group_by { |u| u[:role] }
# => {
# "admin" => [{ name: "Alice", ... }, { name: "Carol", ... }],
# "user" => [{ name: "Bob", ... }, { name: "Dave", ... }]
# }
Building Hashes from Arrays
# From an array of pairs
pairs = [[:name, "Ruby"], [:version, "3.3"], [:type, "language"]]
pairs.to_h
# => { name: "Ruby", version: "3.3", type: "language" }
# From a flat array with each_slice
flat = ["name", "Ruby", "version", "3.3"]
flat.each_slice(2).to_h
# => { "name" => "Ruby", "version" => "3.3" }
# Index by a key
users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
users.index_by { |u| u[:id] } # Rails only
users.to_h { |u| [u[:id], u] } # Pure Ruby 2.6+
# => { 1 => { id: 1, name: "Alice" }, 2 => { id: 2, name: "Bob" } }
Set Operations
Ruby arrays support union, intersection, and difference using operators.
a = [1, 2, 3, 4, 5]
b = [3, 4, 5, 6, 7]
a | b # => [1, 2, 3, 4, 5, 6, 7] (union)
a & b # => [3, 4, 5] (intersection)
a - b # => [1, 2] (difference)
b - a # => [6, 7]
# Symmetric difference (elements in either but not both)
(a | b) - (a & b)
# => [1, 2, 6, 7]
Chaining with then/yield_self
# Pipeline style: transform an array step by step
result = [3, 1, 4, 1, 5, 9, 2, 6]
.sort
.uniq
.then { |arr| arr.select { |n| n > 3 } }
.map { |n| "Item #{n}" }
# => ["Item 4", "Item 5", "Item 6", "Item 9"]
Safe Navigation with dig and fetch
config = [
{ db: { host: "localhost", port: 5432 } },
{ db: { host: "replica.internal", port: 5433 } }
]
# dig for safe nested access
config.dig(0, :db, :host) # => "localhost"
config.dig(5, :db, :host) # => nil
# fetch with a default (raises KeyError if no default given)
arr = [10, 20, 30]
arr.fetch(1) # => 20
arr.fetch(99, "missing") # => "missing"
arr.fetch(99) { |i| "no index #{i}" }
# => "no index 99"
Partition
partition splits an array into two based on a condition — like running select and reject in a single pass.
numbers = (1..10).to_a
evens, odds = numbers.partition(&:even?)
# evens => [2, 4, 6, 8, 10]
# odds => [1, 3, 5, 7, 9]
product (Cartesian Product)
sizes = %w[S M L]
colors = %w[red blue]
sizes.product(colors)
# => [["S", "red"], ["S", "blue"],
# ["M", "red"], ["M", "blue"],
# ["L", "red"], ["L", "blue"]]
Related reading: For the original introductory tutorial, see Ruby Arrays — Basics. To learn about another core collection type, check out Ruby Hashes.