Table of Contents

The JavaScript Array shift() Method is a built-in JavaScript array method used to remove the first element from an array. It modifies the original array by removing the element at index 0 and shifting all remaining elements one position to the left. The shift() method returns the removed element and automatically decreases the array’s length by one. It is commonly used for processing queues, managing task lists, handling incoming messages, and implementing First-In, First-Out (FIFO) data structures.

Syntax and Formal Specification

The method signature is shift(). It takes no arguments. Any arguments passed are silently ignored.

javascript
const queue = ["first", "second", "third"];
const removed = queue.shift();
console.log(removed);   // "first"
console.log(queue);     // ["second", "third"]

Parameters

  • None. shift() accepts no arguments. Passing arguments has no effect and does not throw.

javascript
const arr = [1, 2, 3];
arr.shift("ignored", 42);
console.log(arr);   // [2, 3]

Return Value

  • The first element of the array, if the array is non-empty.

  • undefined if the array is empty.

javascript
const empty = [];
console.log(empty.shift());    // undefined
console.log(empty.length);     // 0
console.log(empty);            // []

The Generic Algorithm

Like other mutating array methods, shift() is generic. It operates on any object with a length property and integer-indexed properties. The specification algorithm, simplified:

  1. Let O be the object on which shift() is invoked (this).

  2. Convert O.length to an integer len.

  3. If len === 0, set O.length = 0 and return undefined.

  4. Read first = O[0].

  5. For k from 1 to len - 1, copy O[k] to O[k - 1].

  6. Delete the property O[len - 1].

  7. Set O.length = len - 1.

  8. Return first.

The leftward shift loop is the source of shift()‘s linear cost: every remaining element moves one slot closer to index 0.

javascript
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
const first = Array.prototype.shift.call(arrayLike);

console.log(first);              // "a"
console.log(arrayLike[0]);       // "b"
console.log(arrayLike[1]);       // "c"
console.log(arrayLike[2]);       // undefined
console.log(arrayLike.length);   // 2

Strings cannot serve as this because they are immutable; indexed assignments fail silently in sloppy mode or throw in strict mode.

javascript
"use strict";
try {
  Array.prototype.shift.call("abc");
} catch (e) {
  console.log(e.constructor.name);   // "TypeError"
}

Performance: O(n) Removal and the Quadratic Trap

shift() is O(n) per call. Every remaining element moves one position lower in memory, and this cost grows with array size. Iterating shift() in a loop yields O(n²) behavior—a subtle but serious performance trap.

javascript
// Slow: O(n) per call, O(n^2) overall
const queue = [];
for (let i = 0; i < 100000; i++) queue.push(i);
for (let i = 0; i < 100000; i++) queue.shift();   // Very slow

// Fast: O(1) amortized per call
const stack = [];
for (let i = 0; i < 100000; i++) stack.push(i);
for (let i = 0; i < 100000; i++) stack.pop();     // Cheap

For 100,000 elements, the shift() loop cumulatively performs roughly 5 billion internal assignments; the pop() loop performs about 100,000. Engines cannot optimize shift() away because the shifting is observable through indexed access and iterators.

When shift() Is Acceptable

shift() is fine when:

  • The array is small (dozens to hundreds of elements).

  • The operation is infrequent (one or two removals per action).

  • Readability outranks micro-optimization.

javascript
// Perfectly reasonable
const nextJob = ["compile", "test", "deploy"];
const first = nextJob.shift();
console.log(first);       // "compile"
console.log(nextJob);     // ["test", "deploy"]

Alternatives for Efficient FIFO

For queues over large arrays, avoid shift() and use one of the following patterns.

1. Head index over push() — Maintain a pointer to the logical front; periodically compact.

javascript
class Queue {
  constructor() { this.items = []; this.head = 0; }
  enqueue(x) { this.items.push(x); }
  dequeue() {
    if (this.head >= this.items.length) return undefined;
    return this.items[this.head++];
  }
  get size() { return this.items.length - this.head; }
}

const q = new Queue();
for (let i = 0; i < 5; i++) q.enqueue(i);
console.log(q.dequeue());   // 0
console.log(q.dequeue());   // 1
console.log(q.size);        // 3

Periodically compact when head becomes large:

javascript
compact() {
  if (this.head > 1000) {
    this.items = this.items.slice(this.head);
    this.head = 0;
  }
}

2. Ring buffer — Fixed-size circular queue with amortized O(1) operations at both ends.

javascript
class RingBuffer {
  constructor(capacity) {
    this.buffer = new Array(capacity);
    this.capacity = capacity;
    this.head = 0;
    this.tail = 0;
    this.size = 0;
  }
  enqueue(x) {
    if (this.size === this.capacity) throw new Error("Full");
    this.buffer[this.tail] = x;
    this.tail = (this.tail + 1) % this.capacity;
    this.size++;
  }
  dequeue() {
    if (this.size === 0) return undefined;
    const x = this.buffer[this.head];
    this.buffer[this.head] = undefined;
    this.head = (this.head + 1) % this.capacity;
    this.size--;
    return x;
  }
}

const rb = new RingBuffer(3);
[1, 2, 3].forEach(n => rb.enqueue(n));
console.log(rb.dequeue());   // 1
rb.enqueue(4);
console.log(rb.dequeue());   // 2

3. Reversed push/pop — If order is not user-visible, store newest at the end and pop instead of shift.

javascript
// FIFO with pop: enqueue pushes to a staging array, dequeue pops
const staging = [];
const delivery = [];

function enqueue(x) { staging.push(x); }
function dequeue() {
  if (delivery.length === 0) {
    while (staging.length) delivery.push(staging.pop());
  }
  return delivery.pop();
}

enqueue("a"); enqueue("b"); enqueue("c");
console.log(dequeue());   // "a"
console.log(dequeue());   // "b"

This “two-stack queue” provides amortized O(1) enqueue and dequeue using only push() and pop().

Distinguishing shift() from pop(), splice(), and slice()

  • shift() — Mutates. Removes first element. O(n).

  • pop() — Mutates. Removes last element. O(1).

  • splice(0, 1) — Mutates. Removes first element. O(n) with higher constants than shift().

  • slice(1) — Returns new array without first element. Non-mutating.

javascript
const base = [1, 2, 3];

const a = base.slice();
console.log(a.shift());       // 1
console.log(a);               // [2, 3]

const b = base.slice();
console.log(b.pop());         // 3
console.log(b);               // [1, 2]

const c = base.slice();
console.log(c.splice(0, 1));  // [1]
console.log(c);               // [2, 3]

console.log(base.slice(1));   // [2, 3]
console.log(base);            // [1, 2, 3]

For pure first-element removal, shift() is faster than splice(0, 1) because splice engages the general-purpose insertion/removal algorithm.

shift() on arguments Objects

Legacy code sometimes uses Array.prototype.shift.call(arguments) to remove the first argument inside a function. Rest parameters make this unnecessary in modern JavaScript.

javascript
function legacy() {
  const first = Array.prototype.shift.call(arguments);
  return { first, remaining: Array.from(arguments) };
}
console.log(legacy("a", "b", "c"));
// { first: "a", remaining: ["b", "c"] }

function modern(...args) {
  const first = args.shift();
  return { first, remaining: args };
}
console.log(modern("a", "b", "c"));
// { first: "a", remaining: ["b", "c"] }

shift() on Frozen and Sealed Arrays

Frozen arrays reject shift() with a TypeError in strict mode. Sealed arrays also reject it because shift() requires deleting the final index, which sealing prevents.

javascript
"use strict";

const frozen = Object.freeze([1, 2, 3]);
try {
  frozen.shift();
} catch (e) {
  console.log(e.constructor.name);   // "TypeError"
}

const sealed = Object.seal([1, 2, 3]);
try {
  sealed.shift();
} catch (e) {
  console.log(e.constructor.name);   // "TypeError"
}

shift() and Typed Arrays

Typed arrays (Uint8Array, Float64Array, etc.) do not expose shift() on their prototypes, and Array.prototype.shift cannot be applied to them because their length is read-only and their indexed properties are not deletable. Use subarray:

javascript
const typed = new Uint8Array([1, 2, 3, 4]);
const withoutFirst = typed.subarray(1);
console.log(withoutFirst);   // Uint8Array [2, 3, 4]
console.log(typed);          // Uint8Array [1, 2, 3, 4]