Queues: Concepts and Implementation

Learning Objectives

Core Concept: What is a Queue?

A Queue is a linear data structure that strictly follows the First-In-First-Out (FIFO) principle. The first element added to the queue will be the first one to be removed.

Real-World Applications:

  • Printer Spooling: Documents sent to a printer are printed in the exact order they were received.
  • Ticket Counters: People wait in a line; the person at the front gets served first.
  • Customer Service Calls: "Your call is important to us. You are currently number 3 in the queue."

FIFO: First-In-First-Out

OUT
1st
2nd
3rd
IN

Data flows in one direction. It enters from the back and leaves from the front, just like a pipe or a tunnel.

Think About It: How do we build this?

If we use a standard array to represent a queue, people join at the back and leave from the front. How does the computer know where the "front" and "back" are at any given time?

Answer: We need TWO variables (pointers)! One to track the front (Head) for removing items, and one to track the back (Tail) for adding new items.

Interactive Queue Simulator

Click Enqueue to add an item, and Dequeue to remove an item. Watch how the Head and Tail pointers move independently. Pay close attention to what happens when the Tail reaches the end!

Head
Tail

Linear Queue Implementation Details

Review the code below. Notice how the array size is fixed, and how the is_empty() and is_full() functions rely entirely on the positions of the Head and Tail pointers, not the actual number of items.

class LinearQueue:
    def __init__(self, size):
        self.max_size = size
        self.queue = [None] * size
        self.head = 0  # Points to the front (for Dequeue)
        self.tail = 0  # Points to the next empty slot at the back (for Enqueue)

    def is_empty(self):
        # Empty when both pointers point to the same index
        return self.head == self.tail

    def is_full(self):
        # Full when tail reaches the absolute end of the array
        # This causes the "Drifting Problem" (False Overflow)
        return self.tail == self.max_size

    def enqueue(self, item):
        if self.is_full():
            print("Queue is Full! (False Overflow may occur)")
            return
        self.queue[self.tail] = item
        self.tail += 1  # Move tail pointer to the right

    def dequeue(self):
        if self.is_empty():
            print("Queue is Empty!")
            return None
        item = self.queue[self.head]
        self.queue[self.head] = None # Clear slot (optional, for visualization)
        self.head += 1  # Move head pointer to the right
        return item

FAQ: Why do we just move the Head pointer instead of shifting all items forward?

Shifting all items forward every time we Dequeue takes too much time (O(N) time complexity). Moving the Head pointer is instant (O(1) time complexity), which is why we do it, even though it leads to the Drifting problem.

Stacks vs Queues

Feature Queue Stack
Principle FIFO LIFO
Insertion Enqueue (at Tail) Push (at Top)
Deletion Dequeue (at Head) Pop (at Top)
Pointers Head & Tail (2) Top (1)

Exam Trap Warning

Common Mistake:

Assuming that a linear queue is full ONLY when all slots are physically occupied by data.

The Reality:

Because of the "Drifting" problem, the Tail pointer can reach the end of the array (indicating it's "full" to the computer) even if there are empty slots at the front due to previous Dequeue operations.

Exam Tip:

In exams, carefully trace the Head and Tail pointers. If asked how to solve the wasted space issue, always answer: "Use a Circular Queue".

Moving Forward: How do we fix this?

We know shifting elements is too slow, and leaving empty space is wasteful. What if the end of the array could magically connect back to the empty spaces at the beginning? Let's look at the Circular Queue.

The Solution: Circular Queue

To solve the Drifting Problem, we logically connect the end of the array back to the beginning, creating a circle. When the Tail or Head pointer reaches the end of the array, it wraps around back to index 0.

The Magic Formula: Modulo Arithmetic

Next Index = (Current Index + 1) % MaxSize

The modulo operator (%) gives the remainder of a division. If MaxSize is 5, and Tail is at index 4, the next position is (4 + 1) % 5 = 0. It loops perfectly!

Circular Queue Pointer Movement (Python)

# Enqueue (Moving Tail)
self.tail = (self.tail + 1) % self.max_size

# Dequeue (Moving Head)
self.head = (self.head + 1) % self.max_size

Test Your Knowledge

1. What causes the "Drifting Problem" in a linear queue?

A) The array size is too small.
B) Shifting all elements forward during Dequeue.
C) Moving the Head pointer during Dequeue without shifting elements, leaving empty spaces behind.

2. If a Circular Queue has a MaxSize of 6, and the Tail is currently at index 5, what will be the new Tail index after one Enqueue operation?

A) 0
B) 6
C) 1