Overview
Ruby arrays support multiple access and assignment patterns through bracket notation and specialized methods. The Array class implements #[]
for element access and #[]=
for element assignment, supporting integer indices, ranges, and slice operations.
Arrays use zero-based indexing with support for negative indices that count from the end. Ruby returns nil
for out-of-bounds access and expands arrays automatically during assignment operations. The bracket operators handle both single element operations and slice operations that work with multiple elements.
arr = [10, 20, 30, 40, 50]
arr[0] # => 10
arr[-1] # => 50
arr[1, 3] # => [20, 30, 40]
Array access operations integrate with Ruby's method call syntax, allowing bracket notation to function as syntactic sugar for method calls. The #[]
method accepts integers, ranges, or two integers representing start position and length.
arr = ['a', 'b', 'c', 'd']
arr[1..2] # => ["b", "c"]
arr[1, 2] # => ["b", "c"]
arr.slice(1, 2) # => ["b", "c"]
Assignment operations modify arrays in place and can extend array bounds automatically. Ruby fills gaps with nil
values when assigning beyond current array boundaries. Multiple assignment patterns support replacing single elements, slices, or inserting new elements.
Basic Usage
Single element access uses integer indices within square brackets. Positive indices start from zero at the beginning, while negative indices start from -1 at the end. Out-of-bounds access returns nil
without raising exceptions.
colors = ['red', 'green', 'blue', 'yellow']
colors[0] # => "red"
colors[3] # => "yellow"
colors[4] # => nil
colors[-1] # => "yellow"
colors[-4] # => "red"
colors[-5] # => nil
Range-based access extracts slices using inclusive or exclusive ranges. Inclusive ranges include both endpoints, while exclusive ranges exclude the final element. Range access returns new arrays containing the selected elements.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers[2..5] # => [3, 4, 5, 6]
numbers[2...5] # => [3, 4, 5]
numbers[-3..-1] # => [8, 9, 10]
numbers[7..-1] # => [8, 9, 10]
Two-argument access specifies starting index and length, providing an alternative to range syntax. The first argument indicates the starting position, and the second specifies how many elements to extract. This pattern works with both positive and negative starting indices.
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
letters[1, 3] # => ["b", "c", "d"]
letters[4, 2] # => ["e", "f"]
letters[-3, 2] # => ["e", "f"]
letters[2, 10] # => ["c", "d", "e", "f", "g"]
Single element assignment replaces existing values or extends arrays when assigning beyond current bounds. Ruby automatically expands arrays and fills intermediate positions with nil
values when necessary.
data = [100, 200, 300]
data[1] = 250 # data => [100, 250, 300]
data[3] = 400 # data => [100, 250, 300, 400]
data[6] = 700 # data => [100, 250, 300, 400, nil, nil, 700]
data[-1] = 750 # data => [100, 250, 300, 400, nil, nil, 750]
Advanced Usage
Multi-element assignment supports replacing slices with arrays of different lengths. When the replacement array has fewer elements than the slice being replaced, the array contracts. When the replacement has more elements, the array expands to accommodate the new data.
sequence = [1, 2, 3, 4, 5, 6, 7, 8, 9]
sequence[2, 3] = [30, 40] # => [1, 2, 30, 40, 6, 7, 8, 9]
sequence[1, 2] = [20, 25, 27, 29] # => [1, 20, 25, 27, 29, 40, 6, 7, 8, 9]
sequence[3, 4] = [] # => [1, 20, 25, 6, 7, 8, 9]
Range assignment replaces elements within the specified range with new values. The replacement can contain any number of elements, causing the array to expand or contract as needed. Range assignment provides precise control over which elements get replaced.
items = ['apple', 'banana', 'cherry', 'date', 'elderberry']
items[1..3] = ['blueberry', 'coconut']
# => ['apple', 'blueberry', 'coconut', 'elderberry']
items[2...2] = ['cantaloupe', 'durian']
# => ['apple', 'blueberry', 'cantaloupe', 'durian', 'coconut', 'elderberry']
Slice assignment with zero length inserts elements at specific positions without replacing existing elements. This pattern uses a starting index with length zero, effectively performing insertion operations at precise locations.
roster = ['Alice', 'Bob', 'Charlie']
roster[1, 0] = ['Alex'] # => ['Alice', 'Alex', 'Bob', 'Charlie']
roster[3, 0] = ['Betty'] # => ['Alice', 'Alex', 'Bob', 'Betty', 'Charlie']
roster[-1, 0] = ['Carl'] # => ['Alice', 'Alex', 'Bob', 'Betty', 'Carl', 'Charlie']
Complex access patterns combine multiple operations for sophisticated array manipulation. These patterns support building data transformation pipelines and handling nested array structures with precise element selection.
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix[1][2] = 60 # => [[1, 2, 3], [4, 5, 60], [7, 8, 9]]
coordinates = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16]]
coordinates[2..-1].map { |pair| pair[1] } # => [4, 9, 16]
nested_data = [['a', 'b'], ['c', 'd', 'e'], ['f']]
nested_data.map { |sub| sub[1..-1] || [] } # => [["b"], ["d", "e"], []]
Performance & Memory
Array access performance varies significantly based on access patterns and array size. Single element access by index operates in constant time O(1) regardless of array size, while slice operations require time proportional to the slice length.
# Benchmark different access patterns
large_array = (1..1_000_000).to_a
# O(1) - constant time
element = large_array[500_000]
# O(n) - linear with slice size
small_slice = large_array[0, 1000] # Fast
medium_slice = large_array[0, 50_000] # Slower
large_slice = large_array[0, 500_000] # Much slower
Memory allocation behavior differs between access and assignment operations. Element access creates minimal overhead, while slice operations allocate new arrays. Assignment operations may trigger array reallocation when expanding beyond current capacity.
original = [1, 2, 3, 4, 5] * 100_000
# No new allocation - returns reference to existing element
single_element = original[50_000]
# Allocates new array with copied elements
slice_copy = original[10_000, 20_000]
# May trigger reallocation if expanding significantly
original[600_000] = 99 # Likely causes reallocation and nil filling
Assignment patterns that cause frequent reallocations impact performance substantially. Growing arrays incrementally by single elements at the end triggers fewer reallocations than assignments at arbitrary positions beyond current bounds.
# Efficient growth pattern
efficient_array = []
1000.times { |i| efficient_array[i] = i * 2 }
# Inefficient growth pattern - causes multiple reallocations
inefficient_array = []
1000.times { |i| inefficient_array[i * 2] = i } # Creates many gaps
Cache performance affects access patterns for large arrays. Sequential access patterns utilize processor cache effectively, while random access patterns may cause cache misses that slow operations.
huge_array = (1..10_000_000).to_a
# Cache-friendly sequential access
sequential_sum = 0
huge_array.each_with_index { |val, idx| sequential_sum += huge_array[idx] }
# Cache-unfriendly random access
random_indices = (0...10_000_000).to_a.shuffle[0, 100_000]
random_sum = random_indices.sum { |idx| huge_array[idx] }
Common Pitfalls
Index boundary errors occur when accessing elements beyond array bounds, but Ruby returns nil
instead of raising exceptions. This behavior can mask logical errors where code expects valid elements but receives nil
values.
short_list = ['first', 'second']
result = short_list[5] # => nil (not an error)
# Problem: nil propagates through calculations
doubled = result * 2 # NoMethodError: undefined method `*' for nil:NilClass
# Solution: Check bounds or use fetch
safe_result = short_list.fetch(5, 'default') # => "default"
Negative index confusion happens when developers misunderstand which element negative indices reference. The index -1 refers to the last element, -2 to the second-to-last, continuing backward through the array.
values = [10, 20, 30, 40, 50]
values[-1] # => 50 (last element, not first)
values[-5] # => 10 (first element with negative index)
values[-6] # => nil (beyond beginning)
# Common mistake: expecting -0 to work
values[-0] # => 10 (same as values[0])
Slice assignment length mismatches cause arrays to expand or contract unexpectedly. When replacement arrays differ in size from the slice being replaced, the total array length changes, potentially affecting subsequent code that assumes fixed positions.
items = ['a', 'b', 'c', 'd', 'e']
items[1, 2] = ['x'] # => ['a', 'x', 'd', 'e'] (contracted)
items[2, 1] = ['y', 'z'] # => ['a', 'x', 'y', 'z', 'e'] (expanded)
# Original indices no longer valid
# items[4] was 'e', now undefined behavior in subsequent code
Nil gap creation during sparse assignment fills intermediate positions with nil
values, creating arrays with unexpected nil
elements that can cause errors in iteration or calculation operations.
sparse = [1, 2, 3]
sparse[10] = 99 # => [1, 2, 3, nil, nil, nil, nil, nil, nil, nil, 99]
# Problems with nil gaps
total = sparse.sum # NoMethodError: String can't be coerced into Integer
compact_total = sparse.compact.sum # => 105 (safe approach)
Range direction errors occur when end index is smaller than start index, or when using inappropriate range types. Ruby returns empty arrays for invalid ranges rather than raising errors.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers[5..2] # => [] (end < start)
numbers[10..15] # => nil (start beyond bounds)
numbers[3...3] # => [] (exclusive range with same start/end)
# Debugging: Check range validity
start_idx, end_idx = 5, 2
if start_idx <= end_idx
slice = numbers[start_idx..end_idx]
else
# Handle invalid range
slice = []
end
Reference
Array Access Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#[](index) |
index (Integer) |
Object or nil |
Returns element at index position |
#[](start, length) |
start (Integer), length (Integer) |
Array or nil |
Returns slice starting at start with length elements |
#[](range) |
range (Range) |
Array or nil |
Returns slice covering range |
#slice(index) |
index (Integer) |
Object or nil |
Alias for #[] with single index |
#slice(start, length) |
start (Integer), length (Integer) |
Array or nil |
Alias for #[] with start and length |
#slice(range) |
range (Range) |
Array or nil |
Alias for #[] with range |
#fetch(index) |
index (Integer) |
Object |
Returns element at index, raises IndexError if out of bounds |
#fetch(index, default) |
index (Integer), default (Object) |
Object |
Returns element at index or default if out of bounds |
#at(index) |
index (Integer) |
Object or nil |
Returns element at index, supports negative indices |
#first |
None | Object or nil |
Returns first element |
#first(n) |
n (Integer) |
Array |
Returns first n elements |
#last |
None | Object or nil |
Returns last element |
#last(n) |
n (Integer) |
Array |
Returns last n elements |
Array Assignment Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#[]=(index, value) |
index (Integer), value (Object) |
Object |
Assigns value at index position |
#[]=(start, length, values) |
start (Integer), length (Integer), values (Array) |
Array |
Replaces slice with values array |
#[]=(range, values) |
range (Range), values (Array) |
Array |
Replaces range slice with values |
#fill(value) |
value (Object) |
Array |
Fills entire array with value |
#fill(value, start) |
value (Object), start (Integer) |
Array |
Fills from start to end with value |
#fill(value, start, length) |
value (Object), start (Integer), length (Integer) |
Array |
Fills slice with value |
#fill(value, range) |
value (Object), range (Range) |
Array |
Fills range with value |
Index Behavior
Index Type | Behavior | Example | Result |
---|---|---|---|
Positive in bounds | Returns element at position | [1,2,3][1] |
2 |
Positive out of bounds | Returns nil | [1,2,3][5] |
nil |
Negative in bounds | Counts from end | [1,2,3][-1] |
3 |
Negative out of bounds | Returns nil | [1,2,3][-5] |
nil |
Zero | Returns first element | [1,2,3][0] |
1 |
Slice Operation Patterns
Operation | Syntax | Behavior | Example |
---|---|---|---|
Range inclusive | arr[start..end] |
Includes both start and end indices | [1,2,3,4][1..2] → [2,3] |
Range exclusive | arr[start...end] |
Includes start, excludes end | [1,2,3,4][1...3] → [2,3] |
Start and length | arr[start, length] |
Length elements from start | [1,2,3,4][1,2] → [2,3] |
Assignment replacement | arr[start, length] = values |
Replaces slice with values | arr[1,2] = [9,8] |
Assignment insertion | arr[index, 0] = values |
Inserts values at index | arr[2,0] = [5,6] |
Error Conditions
Condition | Method | Result |
---|---|---|
Index beyond bounds | #[] |
Returns nil |
Index beyond bounds | #fetch |
Raises IndexError |
Negative start with length | #[start, length] |
May return nil or partial array |
Invalid range | #[range] |
Returns empty array or nil |
Non-integer index | #[] |
Raises TypeError |
Non-array assignment | #[]= with slice |
Attempts conversion with to_ary |