Array#insert
insert(index, *objects) → self Array#insert is the positional sibling of push and unshift. Give it an index and one or more values, and the values land at that position while the original neighbors are shifted out of the way. The call mutates the receiver and returns the same list.
Signature
arr.insert(index, *objects) → self
The first argument is mandatory. Zero or more values follow; if you pass none, the call is a no-op. There is no non-mutating form and no Array#insert! variant.
Basic Examples
Insert a single value at a position:
queue = ["alice", "bob", "dave"]
queue.insert(2, "carol")
# => ["alice", "bob", "carol", "dave"]
The values that follow the index can be any Ruby objects — strings, symbols, numbers, even other lists. Each one lands as its own element at the target position, and Ruby shifts the original neighbors out of the way in a single pass. That is what makes insert a clean replacement for a splice loop when you have more than one value to add. Insert several values in one call:
letters = %w[a b e f]
letters.insert(2, "c", "d")
# => ["a", "b", "c", "d", "e", "f"]
This trick is the one place where insert overlaps with push. The index equal to size is exactly the first slot past the end of the receiver, so the new values land in order with no extra nil padding. Insert at the very end using a positive index equal to the size:
stack = [1, 2, 3]
stack.insert(stack.size, 4)
# => [1, 2, 3, 4]
For appending, push (or its append alias, added in Ruby 3.1) reads better and avoids the size lookup. The same goes for unshift/prepend at the start.
Negative Indices
Negative indices count back from the end, with one twist: a negative index inserts after the element at that offset, not at it. The C source resolves pos < 0 to pos + 1 and splices the new values in at that position.
nums = [10, 20, 30, 40]
nums.insert(-2, 99)
# => [10, 20, 30, 99, 40]
Watch out for insert(-1, x). On a non-empty list, -1 resolves to “after the element at index -1” — which is the second-to-last slot. So 99 lands in the middle, not at the end:
a = [1, 2, 3]
a.insert(-1, 99)
# => [1, 2, 99, 3]
a.push(99)
# => [1, 2, 3, 99] # this is what you probably wanted
The same -1 on an empty list short-circuits to the end (pos == -1 with a zero-length receiver resolves to 0), so [].insert(-1, :a) returns [:a]. Don’t lean on that — it’s an edge case, not a feature.
Out-of-range positive indices
A positive index larger than the current size does not raise. Ruby pads the gap with nil and then appends the new values:
sparse = [1, 2, 3]
sparse.insert(6, :end)
# => [1, 2, 3, nil, nil, nil, :end]
The rule is pos > length, strictly greater. So insert(size, x) on a non-empty list still works as a plain append with no nil padding.
Errors
The only error case is a negative index that is too far back, combined with objects to insert:
[:a, :b].insert(-10, :z)
# IndexError: index -10 too small for array; minimum: -3
The negative-index check only fires when there is something to insert. With no values to place, the method walks the index, finds it out of range, and just returns self unchanged. This is the only case where insert is safe to call with an out-of-range negative index, because the guard never trips. Without objects, the same out-of-range negative index is silently ignored:
[1, 2, 3].insert(-50) # => [1, 2, 3]
[1, 2, 3].insert(50) # => [1, 2, 3]
[1, 2, 3].insert(-50, :z) # IndexError
Calling insert with no arguments at all trips the C arity check (it requires at least the index), not a Ruby ArgumentError.
Splat does not flatten
insert takes a splat, but each object after index becomes one element. Nested lists are inserted as-is:
a = [1, 2, 3]
a.insert(1, [4, 5], 6)
# => [1, [4, 5], 6, 2, 3]
Most new Ruby developers expect the splat in insert’s signature to flatten lists, since splat usually flattens. It does not — the splat is collecting the parameter list, not unpacking one. To unpack a list and merge its elements into the receiver, prefix the value with * at the call site, the same way you would for any other method. To splat another list in, use the splat operator at the call site:
a.insert(1, *[4, 5], 6)
# => [1, 4, 5, 6, 2, 3]
Choosing Between insert, push, unshift, and []=
| Operation | Method | Notes |
|---|---|---|
| Append to the end | push / append | append is an alias added in Ruby 3.1 |
| Prepend to the start | unshift / prepend | prepend is an alias added in Ruby 3.1 |
| Insert at an arbitrary position | insert | Supports negative indices and splat |
| Replace the element at an index | arr[i] = value | Mutates, does not shift neighbors |
| Remove the element at an index | delete_at | The non-insert counterpart |
Use insert when you know the position and want to push neighbors out of the way. Use []= when you want to replace without shifting. Use push/unshift when the position is “end” or “start” — they read better and skip the size lookup the insert version needs.