In Ruby, how do you use an Array as a stack and as a queue, and what do push, pop, shift and unshift return?
answer
- no Stack class; Queue means Thread::Queue
- push and unshift return self
- pop and shift return the element
- empty array: nil, not an error
- pop(2) keeps array order
basics
~10 sIn single-threaded Ruby an Array is both: push with pop is last-in-first-out, push with shift is first-in-first-out. push and unshift return the array; pop and shift return the removed element, or nil when empty.
solid answer
~40 sA stack is `push` (alias `append`) plus `pop`; a queue is `push` plus `shift`, and `unshift` (alias `prepend`) adds to the front. `push` and `unshift` return the array itself, so calls chain, and they take several arguments: `push(a, b)` appends two elements, while `push([a, b])` appends one nested array. `pop` and `shift` return the removed element, and on an empty array they return `nil` rather than raising. With a count they return an Array in the array's own order: `[1, 2, 3].pop(2)` is `[2, 3]`, not `[3, 2]`. Because `nil` also means "empty", check `empty?` before popping when `nil` can be a stored value.
code
ruby · 13 linesundo_stack = []
undo_stack.push(:draw_line, :fill_red) # => [:draw_line, :fill_red]
undo_stack.pop # => :fill_red
undo_stack.pop # => :draw_line
undo_stack.pop # => nil, empty
jobs = []
jobs.push(:export_png).push(:export_svg)
jobs.unshift(:urgent_pdf) # => [:urgent_pdf, :export_png, :export_svg]
jobs.shift # => :urgent_pdf
[1, 2, 3].pop(2) # => [2, 3]
[:a].push([:b, :c]) # => [:a, [:b, :c]]go deeper
Recall which pair of methods gives a stack and which gives a queue, and that pop and shift return nil when empty.
Explain the return values with and without a count, the order of pop(n), and how several arguments differ from one array argument.
Wrap raw arrays so callers cannot break the discipline, avoid storing nil in stacks, and move cross-thread queues to a thread-safe class.
Decide where an in-process array queue is enough and where work must move to a durable queue that survives restarts.
## One class, two disciplines Ruby's core library has **no `Stack` class**, and its top-level `Queue` constant is an alias of **`Thread::Queue`**, a synchronised queue built for passing work between threads. In single-threaded code an `Array` supports both disciplines through four methods that work at its two ends: | Method | Alias | End | Returns | |---|---|---|---| | `push(*objs)` | `append` | back | `self` | | `pop` / `pop(n)` | none | back | element or `nil` / new Array | | `shift` / `shift(n)` | none | front | element or `nil` / new Array | | `unshift(*objs)` | `prepend` | front | `self` | - **Stack (last in, first out):** `push` to add, `pop` to remove. - **Queue (first in, first out):** `push` to enqueue at the back, `shift` to dequeue from the front. - **Front insertion:** `unshift` adds to the front, for example to put an urgent job first. ## Return values worth memorising 1. **`push` and `unshift` return the array**, not the added element, so `stack.push(a).push(b)` chains. 2. **`pop` and `shift` return the removed element**, or **`nil`** when the array is empty. Nothing raises. 3. **With a count**, `pop(n)` and `shift(n)` return a **new Array** holding up to `n` elements in the array's own order: `[1, 2, 3].pop(2)` is `[2, 3]`, and `[1, 2, 3].shift(2)` is `[1, 2]`. Asking for more than exist returns what there is. 4. **Several arguments add several elements**: `a.push(:x, :y)` appends two, and `a.unshift(:x, :y)` prepends them in the order given, so the array starts `[:x, :y, ...]`. 5. **An Array argument is one element**: `a.push([:x, :y])` appends a single nested array. ## The nil ambiguity Because an empty `pop` returns `nil`, a stack that stores `nil` cannot tell "popped a nil" from "was empty". Two habits avoid it: - Check **`empty?`** before popping when the distinction matters. - Keep `nil` out of the collection, which is usually the better design anyway. A `while (item = queue.shift)` loop has the same hazard: it stops at the first `nil` or `false` element, not only at the end. ## A drawing app example A drawing app keeps two collections built on arrays: - An **undo stack** of actions. Each stroke is pushed on; Undo pops the most recent action and reverses it. - A **job queue** of export requests. The UI pushes jobs as the user clicks Export; a worker shifts the oldest one off and renders it. Both are plain arrays; the discipline lives in which methods the code calls, so it helps to wrap them in a small class that exposes only the allowed operations. ## Costs at each end - `push` and `pop` work at the back and are **amortized constant time**: the array grows its buffer in chunks, so most pushes just write into spare room. - `shift` and `unshift` work at the front. In CRuby they are also cheap on large arrays, because the array can move its start pointer instead of moving elements, but that is an interpreter detail worth measuring rather than assuming. - Searching a stack or queue for a particular item is a linear scan either way. ## Wrapping the discipline Exposing a raw Array lets any caller `shift` a stack or `pop` a queue. A thin wrapper keeps the intent visible: - An `UndoStack` class with `record`, `undo` and `empty?` methods delegating to an internal array. - A `JobQueue` class with `enqueue` and `next_job`. When jobs are produced and consumed on **different threads**, a plain Array is the wrong tool; that case calls for the thread-safe `Thread::Queue` (also reachable as `Queue`), which is a separate topic.
- What does [].pop(2) return compared with [].pop?`[].pop` returns `nil`, while `[].pop(2)` returns an empty Array `[]`. With a count, `pop` and `shift` always return an Array of up to that many elements, so the empty case is an empty Array rather than `nil`.
- Why can while (job = jobs.shift) stop before the queue is empty?The loop condition is the shifted value. If the queue holds `nil` or `false`, that element ends the loop and the rest stay queued. Loop on `until jobs.empty?` and shift inside the body when such values are possible.
saying these in an interview costs you the question
- Ruby needs a Stack class from the standard library
- pop on an empty array raises IndexError
- push returns the element that was added
- [1, 2, 3].pop(2) returns [3, 2] in pop order
- push([a, b]) adds a and b as two elements