rubyguides

Array#delete_at

arr.delete_at(index) -> obj or nil

The delete_at method removes and returns the element at a specified index from an array. Unlike delete which removes by value, delete_at targets a specific position.

fruits = ["apple", "banana", "cherry", "date"]
fruits.delete_at(1)
# => "banana"
fruits
# => ["apple", "cherry", "date"]

Syntax

The method takes exactly one argument: the integer position of the element you want to remove. Because Ruby uses zero-based indexing, delete_at(0) always removes the first element while delete_at(-1) always removes the last. The parameter table below covers the expected argument type and its meaning.

arr.delete_at(index)

Parameters

ParameterTypeDescription
indexIntegerThe zero-based position of the element to remove

Return Value

delete_at returns the removed element, or nil if the index is out of range:

The examples above highlight the return value contract: delete_at gives back the element it removed or nil when the index falls outside the bounds. A common mistake is assuming any nil return means the requested position held nil — the out-of-range case below shows why you should check the length separately when that distinction matters.

arr = ["a", "b", "c"]

# Remove element at index 1
arr.delete_at(1)
# => "b"
arr
# => ["a", "c"]

# Index out of bounds returns nil
arr = [1, 2, 3]
arr.delete_at(99)
# => nil
arr
# => [1, 2, 3] — unchanged

The out-of-range case is worth showing because it explains the method’s shape better than the successful path alone. delete_at does not raise here, so the caller can decide whether a missing index is acceptable or whether extra validation is needed before the call. That behavior is handy in scripts that clean up lists defensively.

Basic Examples

Removing by Index

# First element
arr = [1, 2, 3]
arr.delete_at(0)
# => 1
arr
# => [2, 3]

# Last element
arr = [1, 2, 3]
arr.delete_at(-1)
# => 3
arr
# => [1, 2]

# Middle element
arr = ["a", "b", "c", "d"]
arr.delete_at(2)
# => "c"
arr
# => ["a", "b", "d"]

Using negative indices

Negative indices count from the end of the array:

Negative indices count backward from the end, making delete_at(-1) equivalent to pop and delete_at(-2) a way to remove the second-to-last entry. This approach is handy when you know how far from the tail you want to reach but not the exact positive index. The example below removes the penultimate item from a five-element list.

arr = [10, 20, 30, 40, 50]

# Second-to-last element
arr.delete_at(-2)
# => 40
arr
# => [10, 20, 30, 50]

Common use cases

Priority Queue

A priority queue demonstrates delete_at applied to structured data. By removing the element at a fixed position — typically the front — you drain a queue in order. The snippet below pulls the first task from a list of labelled work items, which is the most common queue-draining pattern in simple Ruby scripts.

# Process highest priority item first
queue = [["low", "task1"], ["high", "task2"], ["medium", "task3"]]

# Remove highest priority (index 0)
current = queue.delete_at(0)
current
# => ["low", "task2"]

Slot-based Game

Slot-based inventories naturally map to indexed access. When a game or simulation stores items at numbered positions, delete_at frees a specific slot regardless of what it contains. This example clears slot 2, which happens to hold nil, and the method still returns nil as expected.

# Remove item from a specific slot
inventory = ["sword", "shield", nil, "potion", "armor"]
inventory.delete_at(2)
# => nil
inventory
# => ["sword", "shield", "potion", "armor"]

Dynamic list management

The most basic pattern is removing the front element to process work sequentially. Each call to delete_at(0) pops the head of the queue and shifts the remaining items left by one. The following code shows a single dequeue operation on a short task list.

# Remove processed items
queue = ["task1", "task2", "task3"]
processed = queue.delete_at(0)
# => "task1"
# queue is now ["task2", "task3"]

Comparing with other methods

MethodSearches ByRemovesReturns
delete_atindexSingle elementDeleted element or nil
deletevalueAll matchesDeleted value or nil
sliceindex/rangeElementsDeleted portion or nil
pop-Last elementLast element or nil
shift-First elementFirst element or nil
arr = ["a", "b", "c", "d"]

# delete_at - remove by index
arr = ["a", "b", "c", "d"]
arr.delete_at(1)
# => "b"
arr
# => ["a", "c", "d"]

# delete - remove by value
arr = ["a", "b", "c", "d"]
arr.delete("b")
# => "b"
arr
# => ["a", "c", "d"]

Gotchas

Out of bounds returns nil

Unlike some methods that raise an error, delete_at silently returns nil for invalid indices:

A silent nil on an out-of-range index can hide bugs if you assume every call succeeds. The method does not raise or warn — it simply returns nil and leaves the original list untouched. The example below passes an index far beyond the end of a three-element collection so you can see the no-op behaviour clearly.

arr = [1, 2, 3]
arr.delete_at(100)
# => nil (no error!)
arr
# => [1, 2, 3]

Shifting Indices

After removing an element, all subsequent indices shift down:

Index shifting after a removal is one of the most common sources of bugs when using delete_at in a loop. Each deletion moves every element to the right of the removed position one slot left, which means any stored index values become stale immediately. The following block walks through a concrete example so you can trace the positions.

arr = [0, 1, 2, 3, 4]

# Remove index 2
arr.delete_at(2)
# => 2
arr
# => [0, 1, 3, 4]

# What was at index 3 is now at index 2
arr[2]
# => 3

Mutates original array

delete_at modifies the array in place:

Because delete_at mutates in place, any other variable that points to the same object sees the change. This matters when the original list is passed as an argument or stored in a shared reference. The next block shows how assigning copy = original creates an alias rather than a duplicate, so both variables reflect the mutation.

original = [1, 2, 3]
copy = original
original.delete_at(0)
# => 1
original
# => [2, 3]
copy
# => [2, 3] — also changed

Practical Notes

delete_at is the right choice when position matters more than value. That makes it useful for queues, ordered work lists, and other arrays where the index has meaning. Because the method shifts later items left, follow-up reads should happen after the removal so the code is using the new positions.

If you only need to remove a value regardless of where it appears, delete is often the clearer choice. Picking the method that matches the shape of the data keeps the code easier to understand and helps avoid subtle index bugs.

It also helps to remember that the method mutates the original array. That detail can be useful in simple scripts, but in shared code it is worth making the side effect obvious so nobody accidentally reuses a changed array later in the same flow.

See Also