rubyguides

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 []=

OperationMethodNotes
Append to the endpush / appendappend is an alias added in Ruby 3.1
Prepend to the startunshift / prependprepend is an alias added in Ruby 3.1
Insert at an arbitrary positioninsertSupports negative indices and splat
Replace the element at an indexarr[i] = valueMutates, does not shift neighbors
Remove the element at an indexdelete_atThe 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.

See Also