RubyLearning

Helping Ruby Programmers become Awesome!

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.

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.