Learn queue data structure in C with linear queue, circular queue, and priority queue implementations using arrays and linked lists with complete code examples.
A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. Like a line at a ticket counter, the first person to arrive is the first to be served. Queues are essential in operating systems for process scheduling, in networking for packet buffering, and in breadth-first search algorithms.
Queue Operations
| Operation | Description | Time Complexity |
|---|
| Enqueue | Add element at rear | O(1) |
| Dequeue | Remove element from front | O(1) |
| Front/Peek | View front element | O(1) |
| isEmpty | Check if queue is empty | O(1) |
| isFull | Check if queue is full | O(1) |
Linear Queue Using Array
#include <stdio.h>
#include <stdbool.h>
#define MAX 5
typedef struct {
int items[MAX];
int front;
int rear;
} Queue;
void initQueue(Queue *q) {
q->front = -1;
q->rear = -1;
}
bool isEmpty(Queue *q) {
return q->front == -1;
}
bool isFull(Queue *q) {
return q->rear == MAX - 1;
}
void enqueue(Queue *q, int value) {
if (isFull(q)) {
printf("Queue Overflow! Cannot enqueue %d\n", value);
return;
}
if (isEmpty(q)) q->front = 0;
q->items[++(q->rear)] = value;
printf("Enqueued: %d\n", value);
}
int dequeue(Queue *q) {
if (isEmpty(q)) {
printf("Queue Underflow!\n");
return -1;
}
int value = q->items[q->front];
if (q->front == q->rear) {
q->front = q->rear = -1; // Queue becomes empty
} else {
q->front++;
}
return value;
}
int peek(Queue *q) {
if (isEmpty(q)) return -1;
return q->items[q->front];
}
void display(Queue *q) {
if (isEmpty(q)) { printf("Queue is empty\n"); return; }
printf("Queue: ");
for (int i = q->front; i <= q->rear; i++)
printf("%d ", q->items[i]);
printf("\n");
}
int main() {
Queue q;
initQueue(&q);
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
enqueue(&q, 40);
display(&q);
printf("Dequeued: %d\n", dequeue(&q));
printf("Dequeued: %d\n", dequeue(&q));
printf("Front: %d\n", peek(&q));
display(&q);
return 0;
}
Enqueued: 10
Enqueued: 20
Enqueued: 30
Enqueued: 40
Queue: 10 20 30 40
Dequeued: 10
Dequeued: 20
Front: 30
Queue: 30 40
The problem with linear queues: after dequeuing, front positions are wasted and cannot be reused.
Circular Queue Implementation
A circular queue wraps around, reusing empty positions at the front. This solves the space wastage problem of linear queues.
#include <stdio.h>
#include <stdbool.h>
#define MAX 5
typedef struct {
int items[MAX];
int front;
int rear;
int count;
} CircularQueue;
void initCQ(CircularQueue *q) {
q->front = 0;
q->rear = -1;
q->count = 0;
}
bool isCQEmpty(CircularQueue *q) { return q->count == 0; }
bool isCQFull(CircularQueue *q) { return q->count == MAX; }
void enqueue(CircularQueue *q, int value) {
if (isCQFull(q)) {
printf("Queue Full! Cannot enqueue %d\n", value);
return;
}
q->rear = (q->rear + 1) % MAX;
q->items[q->rear] = value;
q->count++;
printf("Enqueued: %d\n", value);
}
int dequeue(CircularQueue *q) {
if (isCQEmpty(q)) {
printf("Queue Empty!\n");
return -1;
}
int value = q->items[q->front];
q->front = (q->front + 1) % MAX;
q->count--;
return value;
}
void display(CircularQueue *q) {
if (isCQEmpty(q)) { printf("Queue is empty\n"); return; }
printf("Queue [size=%d]: ", q->count);
int i = q->front;
for (int c = 0; c < q->count; c++) {
printf("%d ", q->items[i]);
i = (i + 1) % MAX;
}
printf("\n");
}
int main() {
CircularQueue q;
initCQ(&q);
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
enqueue(&q, 40);
enqueue(&q, 50);
display(&q);
printf("Dequeued: %d\n", dequeue(&q));
printf("Dequeued: %d\n", dequeue(&q));
// Now we can enqueue again (circular reuse)
enqueue(&q, 60);
enqueue(&q, 70);
display(&q);
return 0;
}
Enqueued: 10
Enqueued: 20
Enqueued: 30
Enqueued: 40
Enqueued: 50
Queue [size=5]: 10 20 30 40 50
Dequeued: 10
Dequeued: 20
Enqueued: 60
Enqueued: 70
Queue [size=5]: 30 40 50 60 70
Queue Using Linked List
No size limit – grows dynamically as needed.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
typedef struct {
Node *front;
Node *rear;
int size;
} Queue;
void initQueue(Queue *q) {
q->front = q->rear = NULL;
q->size = 0;
}
bool isEmpty(Queue *q) { return q->front == NULL; }
void enqueue(Queue *q, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = value;
newNode->next = NULL;
if (isEmpty(q)) {
q->front = q->rear = newNode;
} else {
q->rear->next = newNode;
q->rear = newNode;
}
q->size++;
}
int dequeue(Queue *q) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return -1;
}
Node *temp = q->front;
int value = temp->data;
q->front = temp->next;
if (q->front == NULL) q->rear = NULL;
free(temp);
q->size--;
return value;
}
void display(Queue *q) {
Node *temp = q->front;
printf("Queue: ");
while (temp) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("(size: %d)\n", q->size);
}
void freeQueue(Queue *q) {
while (!isEmpty(q)) dequeue(q);
}
int main() {
Queue q;
initQueue(&q);
enqueue(&q, 100);
enqueue(&q, 200);
enqueue(&q, 300);
display(&q);
printf("Dequeued: %d\n", dequeue(&q));
enqueue(&q, 400);
display(&q);
freeQueue(&q);
return 0;
}
Queue: 100 200 300 (size: 3)
Dequeued: 100
Queue: 200 300 400 (size: 3)
Priority Queue Implementation
Elements are dequeued based on priority, not insertion order. Higher priority elements are served first.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct PQNode {
int data;
int priority; // Lower number = higher priority
struct PQNode *next;
} PQNode;
typedef struct {
PQNode *front;
int size;
} PriorityQueue;
void initPQ(PriorityQueue *pq) {
pq->front = NULL;
pq->size = 0;
}
bool isPQEmpty(PriorityQueue *pq) { return pq->front == NULL; }
void pqEnqueue(PriorityQueue *pq, int data, int priority) {
PQNode *newNode = (PQNode*)malloc(sizeof(PQNode));
newNode->data = data;
newNode->priority = priority;
// Insert at correct position based on priority
if (isPQEmpty(pq) || priority < pq->front->priority) {
newNode->next = pq->front;
pq->front = newNode;
} else {
PQNode *temp = pq->front;
while (temp->next != NULL && temp->next->priority <= priority)
temp = temp->next;
newNode->next = temp->next;
temp->next = newNode;
}
pq->size++;
}
int pqDequeue(PriorityQueue *pq) {
if (isPQEmpty(pq)) {
printf("Priority Queue is empty!\n");
return -1;
}
PQNode *temp = pq->front;
int value = temp->data;
pq->front = temp->next;
free(temp);
pq->size--;
return value;
}
void displayPQ(PriorityQueue *pq) {
PQNode *temp = pq->front;
printf("Priority Queue: ");
while (temp) {
printf("[%d|p%d] ", temp->data, temp->priority);
temp = temp->next;
}
printf("\n");
}
int main() {
PriorityQueue pq;
initPQ(&pq);
pqEnqueue(&pq, 100, 3); // Low priority
pqEnqueue(&pq, 200, 1); // Highest priority
pqEnqueue(&pq, 300, 2); // Medium priority
pqEnqueue(&pq, 400, 1); // Highest priority
pqEnqueue(&pq, 500, 4); // Lowest priority
displayPQ(&pq);
printf("Dequeue: %d (highest priority)\n", pqDequeue(&pq));
printf("Dequeue: %d\n", pqDequeue(&pq));
displayPQ(&pq);
return 0;
}
Priority Queue: [200|p1] [400|p1] [300|p2] [100|p3] [500|p4]
Dequeue: 200 (highest priority)
Dequeue: 400
Priority Queue: [300|p2] [100|p3] [500|p4]
Queue Types Comparison
| Type | Best For | Drawback |
|---|
| Linear Queue | Simple FIFO tasks | Wastes space after dequeue |
| Circular Queue | Fixed-size buffers, I/O | Size must be predetermined |
| Linked List Queue | Dynamic size needs | Memory overhead per node |
| Priority Queue | Task scheduling, Dijkstra | Insertion is O(n) |
| Deque (Double-ended) | Both FIFO and LIFO | More complex implementation |
Interview Questions on Queues
Q1: How do you implement a queue using two stacks? Use stack1 for enqueue (push). For dequeue, if stack2 is empty, pop all from stack1 to stack2, then pop from stack2. Amortized O(1) per operation.
Q2: What's the advantage of circular queue over linear queue? Circular queue reuses empty positions created by dequeue operations. In a linear queue, once the rear reaches MAX, you can't enqueue even if front positions are empty.
Q3: Where are queues used in operating systems? Process scheduling (ready queue), I/O request buffering, print spooling, breadth-first search in file systems, and interrupt handling.
Q4: How do you reverse a queue? Use a stack: dequeue all elements into a stack, then pop all elements back into the queue.
Q5: What is a deque (double-ended queue)? A deque allows insertion and deletion at both front and rear. It can function as both a stack (LIFO) and a queue (FIFO).
Summary
Queues are essential for maintaining order in processing systems. Linear queues are simple but waste space; circular queues solve this with wrap-around indexing. Linked list queues offer unlimited dynamic sizing. Priority queues serve elements by importance rather than arrival order. Understanding when to use each variant and implementing them correctly is critical for system design and coding interviews.