Skip to main content

Array Indexing

Updated Aug 24, 2023 ·

Accessing an Element

Arrays keep items in a fixed order, just like characters in a string. Ruby tracks this order using index positions that start at zero (0)

Zero-based Indexing

Consider the example below:

fruits = ["apple", "orange", "grape", "banana"]

Where:

  • Index 0 refers to the first element
  • Index 1 refers to the second element
  • The last index is always length minus one

To access elements by index:

p fruits[0]  # Output: "apple"
p fruits[1] # Output: "orange"
p fruits[3] # Output: "banana"

We can also use the first and last methods:

p fruits.first  # Output: "apple"
p fruits.last # Output: "banana"

If you try to access an index that does not exist, Ruby returns nil, which means no value is there.

p fruits[100]  # nil

Negative Indexing

You can also count from the end of the array using negative numbers.

  • -1 means the last element
  • -2 means the second to last element

Example:

fruits = ["apple", "orange", "grape", "banana"]

p fruits[-1] # "banana"
p fruits[-2] # "grape"
p fruits[-4] # "apple"
p fruits[-10] # nil

Negative indexing is useful when you want values relative to the end without knowing the array length, which again depends on the array’s ordered structure.

Using first and last

Ruby provides two helper methods to quickly get elements from the start or end of an array.

  • first returns the first element or the first N elements.
  • last returns the last element or the last N elements.
  • Passing an argument always returns an array of elements.

For example, with a sushi array:

sushi = ["salmon", "tuna", "shrimp", "eel", "yellowtail"]

p sushi.first # Output: "salmon"
p sushi.first(3) # Output: ["salmon", "tuna", "shrimp"]
p sushi.first(1) # Output: ["salmon"]

p sushi.last # Output: "yellowtail"
p sushi.last(3) # Output: ["shrimp", "eel", "yellowtail"]
p sushi.last(1) # Output: ["yellowtail"]

Output:

"salmon"
"yellowtail"

If called without an argument, first or last returns a single element. When called with a number, they return an array containing that many elements. This makes it easy to extract multiple items from either end of an array without manually using indexes.

p sushi.first(3)       
p sushi.first(1)

p sushi.last(3)
p sushi.last(1)

Output:

["salmon", "tuna", "shrimp"]
["salmon"]

["shrimp", "eel", "yellowtail"]
["yellowtail"]

Using slice

Ruby provides a method alternative to square brackets called slice.

p fruits.slice(0)   # "apple"
p fruits.slice(-3) # "orange"
p fruits.slice(10) # nil

slice behaves the same way as bracket indexing and exists as another way to work with ordered elements.

Using fetch

The fetch method is another way to read values from an array by index:

  • Accepts an index position
  • Supports negative indexes
  • Can return a default value instead of nil

Consider the example below:

airports = ["JFK", "LAX", "ORD", "ATL", "DFW", "DEN"]

We can use fetch with a normal index:

airports.fetch(2)

Output:

"ORD"

We can also use negative indexes to count from the end:

airports.fetch(-2)

Output:

"DFW"

If you request an index that is outside the array range, fetch raises an error instead of returning nil.

airports.fetch(100)

Output:

'Array#fetch': index 100 outside of array bounds: -6...6 (IndexError)

This is different from airports[100], which would quietly return nil. Using fetch makes missing indexes more obvious.

Using fetch with Default Value

fetch allows a second argument that acts as a fallback value.

airports = ["JFK", "LAX", "ORD", "ATL", "DFW", "DEN"]

airports.fetch(100, "Unknown airport")

Output:

"Unknown airport"

If the index exists, the real value is returned:

airports.fetch(1, "Unknown airport")

Output:

"LAX"

The default value is only used when the index does not exist.

Check if Element exists

We can check whether an array contains a value by using include?.

my_list = [11, 8, 5, 14, 10, 23, 28, 12]
p my_list.include?(23)

Output:

true

Arrays can also contain mixed types. When searching for a string, make sure to use double quotes for the string:

mixed = ["apples", 23, 18, "oranges"]
p mixed.include?("bananas")

Output:

false

Overwriting Elements

You can replace a value at a specific index by assigning a new value.

fruits = ["apple", "orange", "grape", "banana"]

fruits[1] = "watermelon"
p fruits

Output:

["apple", "watermelon", "grape", "banana"]

Adding Beyond the Current Length

If you assign a value just past the end of the array, Ruby appends it.

fruits = ["apple", "orange", "grape", "banana"]

fruits[4] = "raspberry"
p fruits

Output:

["apple", "watermelon", "grape", "banana", "raspberry"]

If you assign a value far beyond the end, Ruby fills the gaps with nil.

fruits[10] = "kiwi"
p fruits

Output:

["apple", "watermelon", "grape", "banana", "raspberry", nil, nil, nil, nil, nil, "kiwi"]

Ruby does this to preserve index order, even when values are added far ahead.

Extract and Overwrite Multiple Elements

You can extract or replace several elements in one operation, similar to how you handle characters in a string.

Consider the example below:

sesame_street = [
"Elmo",
"Big Bird",
"Cookie Monster",
"Bert",
"Ernie",
"Oscar"
]

# Extract the first three elements
p sesame_street[0,3]

# Extract four elements starting at index 2
p sesame_street[2,4]

# Using slice method (same results)
p sesame_street.slice(0,3)
p sesame_street.slice(2,4)

Output:

["Elmo", "Big Bird", "Cookie Monster"]

["Cookie Monster", "Bert", "Ernie", "Oscar"]

["Elmo", "Big Bird", "Cookie Monster"]
["Cookie Monster", "Bert", "Ernie", "Oscar"]

You can also overwrite multiple elements:

# Replace Bert and Ernie with Stinky and Kermit
sesame_street[3,2] = ["Stinky", "Kermit"]
p sesame_street

# Replace same indices with three elements
sesame_street[3,2] = ["Bert", "Ernie", "Julia"]
p sesame_street

Output:

["Elmo", "Big Bird", "Cookie Monster", "Stinky", "Kermit", "Oscar"]

["Elmo", "Big Bird", "Cookie Monster", "Bert", "Ernie", "Julia", "Oscar"]

Extract Specific Elements with values_at

You can pick any elements from an array by specifying their index positions. This is useful when elements are not next to each other.

For example, here is an array of TV channels:

channels = ["CBS", "UPN", "CW", "Fox", "NBC", "ESPN"]

# Extract first and fifth elements
p channels.values_at(0,4)

# Extract second, fourth, and last elements
p channels.values_at(1,3,5)

# Mix positive and negative indices
p channels.values_at(1,-1)

# Duplicate indices
p channels.values_at(3,3,-1)

# Access non-existent indices
p channels.values_at(0,1,10,-100)

Output:

["CBS", "NBC"]

["UPN", "Fox", "ESPN"]

["UPN", "ESPN"]

["Fox", "Fox", "ESPN"]

["CBS", "UPN", nil, nil]

values_at allows you to extract elements in any order, include duplicates, and mix positive and negative indices. If an index does not exist, Ruby returns nil.