Back to Queue, Deque & Stack
Q5Question 5 of 8

What are the Queue throwing vs non-throwing methods?

Key Concepts
  • Queue has two method sets: throwing (add/remove/element) and special-value (offer/poll/peek) for the same three operations
  • add() throws IllegalStateException if the queue is full; offer() returns false
  • remove() and element() throw NoSuchElementException if the queue is empty; poll() and peek() return null
  • Always prefer offer/poll/peek for bounded queues and consumer loops where empty or full is a normal condition
  • Use add/remove/element only when failure indicates a programming bug (invariant violation)
  • BlockingQueue adds a third set: blocking methods (put/take) that wait instead of throwing or returning

Definition

Queue offers two method variants for each operation: throwing (add/remove/element) for when failure is a bug, and special-value (offer/poll/peek) for when empty or full is a normal runtime condition.
  • Throwing methods: add(e) throws IllegalStateException if full; remove() and element() throw NoSuchElementException if empty
  • Special-value methods: offer(e) returns false if full; poll() and peek() return null if empty
  • BlockingQueue adds: put(e) blocks if full; take() blocks if empty
  • BlockingQueue also adds: offer(e, timeout, unit) and poll(timeout, unit) for timed blocking
  • The choice between throwing and special-value depends on whether failure is expected (normal) or unexpected (bug)

The throwing methods (add/remove/element) are like a bank teller who shouts an alarm if something goes wrong: "There are no customers in line!" Use them when an empty queue means something is broken. The special-value methods (offer/poll/peek) are like a teller who quietly says "no one is waiting" and continues working. Use them when an empty queue is a normal part of the workflow.

Understanding the Concepts

What are the two method sets?

The Queue interface defines six methods organized into three operations, each with a throwing and a special-value variant:

DIAGRAM
| Operation | Throws exception | Returns special value |
|---|---|---|
| Insert | `add(e)` → IllegalStateException if full | `offer(e)` → false if full |
| Remove | `remove()` → NoSuchElementException if empty | `poll()` → null if empty |
| Examine | `element()` → NoSuchElementException if empty | `peek()` → null if empty |
JAVA
package com.bank.demo;

import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;

public class QueueMethodsDemo {
    public static void main(String[] args) {
        // Bounded queue for demonstration
        Queue<String> queue = new ArrayBlockingQueue<>(2);

        // === INSERT ===
        queue.add("TXN-001");    // OK
        queue.add("TXN-002");    // OK – queue is now full

        // add() on full queue: throws
        try {
            queue.add("TXN-003");
        } catch (IllegalStateException e) {
            System.out.println("add() on full: " + e.getMessage());
        }

        // offer() on full queue: returns false
        boolean accepted = queue.offer("TXN-003");
        System.out.println("offer() on full: " + accepted);

        // === REMOVE ===
        queue.poll(); // TXN-001
        queue.poll(); // TXN-002 – queue is now empty

        // remove() on empty queue: throws
        try {
            queue.remove();
        } catch (NoSuchElementException e) {
            System.out.println("remove() on empty: " + e.getClass().getSimpleName());
        }

        // poll() on empty queue: returns null
        String result = queue.poll();
        System.out.println("poll() on empty: " + result);

        // === EXAMINE ===
        // element() on empty: throws
        try {
            queue.element();
        } catch (NoSuchElementException e) {
            System.out.println("element() on empty: " + e.getClass().getSimpleName());
        }

        // peek() on empty: returns null
        String peeked = queue.peek();
        System.out.println("peek() on empty: " + peeked);
    }
}
TEXT
add() on full: Queue full
offer() on full: false
remove() on empty: NoSuchElementException
poll() on empty: null
element() on empty: NoSuchElementException
peek() on empty: null

When do you use throwing methods vs special-value methods?

  • Use special-value methods (offer/poll/peek) when:
  • Empty or full is a normal runtime condition – not a bug.
  • You are implementing a consumer loop that checks for available items.
  • You are using a bounded queue where the full condition is expected.
  • You are in a time-sensitive path where exceptions are expensive.
  • Use throwing methods (add/remove/element) when:
  • Failure indicates a programming error or violated invariant.
  • The queue should never be empty or full at this point in the code.
  • You want to fail fast with a clear exception message.
JAVA
package com.bank.service;

import java.util.*;
import java.util.concurrent.*;

public class MethodChoiceDemo {
    public static void main(String[] args) {
        // Consumer loop: poll() is correct (empty = normal)
        Queue<String> queue = new ArrayDeque<>();
        queue.offer("TXN-001");
        queue.offer("TXN-002");

        String txn;
        while ((txn = queue.poll()) != null) {
            System.out.println("Processing: " + txn);
        }
        System.out.println("Queue drained");

        // Bounded queue: offer() is correct (full = normal back-pressure)
        BlockingQueue<String> bounded = new ArrayBlockingQueue<>(2);
        bounded.offer("A");
        bounded.offer("B");
        boolean added = bounded.offer("C"); // false – full, no exception
        System.out.println("Offered to full queue: " + added);

        // Invariant check: element() when you EXPECT the queue is not empty
        Queue<String> mustNotBeEmpty = new ArrayDeque<>();
        mustNotBeEmpty.add("REQUIRED");
        String head = mustNotBeEmpty.element(); // throws if empty = BUG
        System.out.println("Head: " + head);
    }
}
TEXT
Processing: TXN-001
Processing: TXN-002
Queue drained
Offered to full queue: false
Head: REQUIRED

What does BlockingQueue add?

BlockingQueue extends Queue with two additional method sets:

| Operation | Throws | Special value | Blocks | Timed |
|---|---|---|---|---|
| Insert | add(e) | offer(e) | put(e) | offer(e, time, unit) |
| Remove | remove() | poll() | take() | poll(time, unit) |
| Examine | element() | peek() | N/A | N/A |

JAVA
package com.bank.service;

import java.util.concurrent.*;

public class BlockingMethodsDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>(2);

        // put() blocks if full
        queue.put("TXN-001"); // OK
        queue.put("TXN-002"); // OK – now full
        // queue.put("TXN-003"); // would BLOCK until space is available

        // take() blocks if empty
        String txn = queue.take(); // TXN-001
        System.out.println("Taken: " + txn);

        // Timed: offer with timeout
        boolean added = queue.offer("TXN-003", 1, TimeUnit.SECONDS);
        System.out.println("Timed offer: " + added);

        // Timed: poll with timeout
        String polled = queue.poll(1, TimeUnit.SECONDS);
        System.out.println("Timed poll: " + polled);
    }
}
TEXT
Taken: TXN-001
Timed offer: true
Timed poll: TXN-002

What are common mistakes?

Mistake 1: Using add() on a bounded queue

JAVA
// WRONG – add() throws when full
BlockingQueue<String> bounded = new ArrayBlockingQueue<>(2);
bounded.add("A");
bounded.add("B");
bounded.add("C"); // IllegalStateException!

// CORRECT – offer() returns false
bounded.offer("C"); // false, no exception

// OR – put() blocks until space available
bounded.put("C"); // waits (needs another thread to take)

Mistake 2: Using poll() with null elements

JAVA
// If your queue allows null elements and you use poll():
Queue<String> queue = new LinkedList<>();
queue.offer(null);
String result = queue.poll(); // null – but was this empty or the null element?
// Ambiguous! That is why BlockingQueue does NOT allow null elements.

What should a senior engineer know?

- Null ambiguity. poll() and peek() return null to signal "empty." If the queue contains null elements, there is no way to distinguish "empty" from "null element." This is why most concurrent queue implementations reject null.

- Exception cost. Throwing and catching exceptions is expensive (~10x slower than a return statement). In a high-throughput consumer loop, using remove() on an empty queue and catching NoSuchElementException wastes CPU. Use poll() instead.

- Deque extends Queue. Deque provides all Queue methods plus double-ended variants: offerFirst/offerLast, pollFirst/pollLast, peekFirst/peekLast.

What are common interview follow-up questions?

  • "What is the difference between add() and offer()?" – add throws, offer returns false.
  • "What is the difference between poll() and take()?" – poll returns null, take blocks.
  • "Why does BlockingQueue not allow null?" – null is the special value returned by poll/peek for empty.
  • "When would you use add() over offer()?" – When failure means a programming bug.

Visual Overview

DIAGRAM
  Queue method matrix

  ┌───────────┬──────────────────┬──────────────────┐
  │ Operation │ Throws Exception │ Returns Special  │
  ├───────────┼──────────────────┼──────────────────┤
  │ Insert    │ add(e) → ISE     │ offer(e) → false │
  │ Remove    │ remove() → NSEE  │ poll() → null    │
  │ Examine   │ element() → NSEE │ peek() → null    │
  └───────────┴──────────────────┴──────────────────┘

  BlockingQueue adds:
  ┌───────────┬──────────────────┬──────────────────┐
  │ Operation │ Blocks           │ Timed Block      │
  ├───────────┼──────────────────┼──────────────────┤
  │ Insert    │ put(e) → waits   │ offer(e,t,u)     │
  │ Remove    │ take() → waits   │ poll(t,u)        │
  └───────────┴──────────────────┴──────────────────┘

  Decision:
  Failure = bug?       → add/remove/element (throw)
  Failure = normal?    → offer/poll/peek (special value)
  Need to wait?        → put/take (block)
  Need timeout?        → offer(e,t,u) / poll(t,u)

Key Takeaway

The Queue interface provides two method variants for each operation. The throwing methods – add, remove, element – signal failure with exceptions and are appropriate when an empty or full queue indicates a programming error. The special-value methods – offer, poll, peek – return false or null and are appropriate when empty or full is a normal runtime condition.

In practice, you almost always use the special-value methods. Consumer loops use poll() because an empty queue means "no work right now" – not a bug. Bounded queues use offer() because a full queue means "slow down" – not a failure. BlockingQueue extends this with put() and take(), which block the calling thread instead of returning immediately.

In the National Bank of India's transaction processor, Rajesh uses poll() in the consumer loop because an empty queue simply means no pending transactions. He uses offer() when submitting transactions to the bounded queue because a full queue triggers back-pressure gracefully.

Remember one sentence: use offer/poll/peek when empty or full is normal, add/remove/element when it is a bug, and put/take when you want to wait.

---

Share
Use ← → arrow keys to navigate