Array manipulation: array_push/pop/shift/unshift, array_splice
Concept
PHP provides a rich set of functions for modifying array contents by adding or removing elements from various positions. Understanding their exact behavior — especially what they return and how they affect keys — prevents subtle bugs when used interchangeably.
array_push() appends one or more values to the end of an array using integer keys. It returns the new count. Since PHP 5.3, the idiomatic $arr[] = $value is preferred over array_push($arr, $value) for single-element appends because it avoids a function call overhead. array_push($arr, ...$values) is still useful when you need to append multiple values at once. array_pop() removes and returns the last element, or null if the array is empty.
array_shift() removes and returns the first element. Because PHP arrays maintain insertion order via the doubly-linked bucket list, removing the first element requires relinking that bucket and reindexing all numeric keys starting from 0. This is an O(n) operation for large arrays with many integer keys — a critical gotcha. Use array_shift() on small arrays or consider SplQueue for queue-like patterns. array_unshift() prepends one or more elements and similarly reindexes, making it O(n).
array_splice($arr, $offset, $length, $replacement) is the Swiss Army knife: it removes $length elements starting at $offset, optionally replaces them with $replacement, and returns the removed elements. Unlike array_slice() (which is non-destructive), array_splice() modifies the original array. String keys in the replacement are coerced to integer keys — this is a known gotcha. For associative arrays with string keys, array_splice() is not appropriate.
One behavioral detail worth knowing: all removal functions (array_pop, array_shift, array_splice) trigger copy-on-write if the array has multiple references, potentially causing an unexpected full copy before the removal.
Code Example
<?php
declare(strict_types=1);
// ---- array_push vs [] syntax ----
$stack = [1, 2, 3];
$stack[] = 4; // preferred for single push
array_push($stack, 5, 6); // append multiple at once
// $stack = [1, 2, 3, 4, 5, 6]
// ---- array_pop — O(1) ----
$last = array_pop($stack); // 6 — modifies $stack
// $stack = [1, 2, 3, 4, 5]
// ---- array_shift — O(n) reindex ----
$queue = ['a', 'b', 'c', 'd'];
$first = array_shift($queue); // 'a' — reindexes [b,c,d] to [0,1,2]
print_r($queue); // [0=>b, 1=>c, 2=>d]
// ---- array_unshift — O(n) reindex ----
array_unshift($queue, 'z', 'y');
print_r($queue); // [0=>z, 1=>y, 2=>b, 3=>c, 4=>d]
// ---- array_splice — in-place slice + replace ----
$colors = ['red', 'green', 'blue', 'yellow'];
// Remove 2 elements starting at offset 1
$removed = array_splice($colors, 1, 2);
// $removed = ['green', 'blue']
// $colors = ['red', 'yellow']
// Insert without removing (length=0)
array_splice($colors, 1, 0, ['purple', 'orange']);
// $colors = ['red', 'purple', 'orange', 'yellow']
// Replace elements
array_splice($colors, 0, 1, ['crimson']);
// Replaces 'red' with 'crimson'
// ---- String keys and splice: gotcha ----
$assoc = ['a' => 1, 'b' => 2, 'c' => 3];
array_splice($assoc, 1, 1, ['new']); // inserts 'new' with integer key 0
// Result is NOT what you'd expect for associative arrays — avoid splice on assoc
// ---- Implementing a bounded queue ----
function enqueueBounded(array &$queue, mixed $item, int $max): void
{
$queue[] = $item;
if (count($queue) > $max) {
array_shift($queue); // O(n) — acceptable for small queues
}
}
$recent = [];
enqueueBounded($recent, 'event1', 3);
enqueueBounded($recent, 'event2', 3);
enqueueBounded($recent, 'event3', 3);
enqueueBounded($recent, 'event4', 3); // evicts 'event1'
print_r($recent); // [0=>event2, 1=>event3, 2=>event4]Interview Q&A
Q: Why is array_shift() O(n) while array_pop() is O(1), and what data structure should you use when you need O(1) dequeue from both ends?
array_pop() removes the last element from PHP's doubly-linked bucket list, which is a constant-time operation — just unlink the tail bucket, free the slot, and update the tail pointer. array_shift() removes the head bucket but must then reindex all remaining integer keys to start from 0, walking every bucket to update its integer key — O(n). For queue patterns requiring O(1) operations at both ends, use SplDoublyLinkedList (or its subclasses SplQueue and SplStack). SplQueue::enqueue() and SplQueue::dequeue() are both O(1) because the underlying C doubly-linked list doesn't maintain key-order invariants the way PHP's HashTable does.
Q: What does array_splice($arr, -2) do, and how does negative offset behavior differ from array_slice()?
Both array_splice() and array_slice() accept negative offsets, counting from the end of the array. array_splice($arr, -2) removes the last 2 elements from $arr in-place and returns them. array_slice($arr, -2) returns a new array containing the last 2 elements without modifying $arr. The in-place vs copy distinction is the key difference. Negative offsets make it easy to express "the last N elements" without calculating count($arr) - N manually. For array_splice() with omitted $length, it removes from $offset to the end of the array.
Q: You are building a message pipeline that processes items FIFO. You have a choice between a plain PHP array with array_shift() and array_push(), or SplQueue. For what array sizes does the PHP array approach become unacceptable, and what is the practical threshold?
The PHP array approach becomes problematic when array_shift() is called frequently on arrays with hundreds or more elements, because each shift is O(n). For a queue of 10,000 elements where you process 10,000 items (one shift per item), the total work is 10,000 + 9,999 + ... + 1 = ~50 million bucket pointer updates. On modern hardware this might take 50–200 ms, which is unacceptable for in-process queues. SplQueue uses a true doubly-linked list with O(1) enqueue() and dequeue(). The practical threshold where SplQueue outperforms array-shift depends on hardware but is typically around 100–500 elements for high-frequency dequeue operations. For small bounded queues (under 50 elements) processed infrequently, plain arrays are fine.