C array-based stacks and circular queues
Build a fixed-capacity stack and a queue that wraps around the end of its array.
On this page
C structs, arrays, and the modulo operator.
- Build a fixed-capacity stack and a queue that wraps around the end of its array.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A stack follows last-in, first-out order. In an array it needs a top index: push stores at top and increments it, and pop decrements it and returns the element. Both are O(1). Because the array has a fixed capacity, push must report failure when the stack is full, and pop must report failure when it is empty.
A queue follows first-in, first-out order. A naive array queue wastes space as the head moves right. A circular queue reuses the freed slots by wrapping indexes with the modulo operator: the next slot after position i is (i + 1) % capacity. A head index and a size are enough to tell the full state from the empty state.
Code example
#include <stdio.h>
#define CAP 4
typedef struct {
int data[CAP];
int top;
} Stack;
static int push(Stack *s, int value) {
if (s->top == CAP) {
return 0;
}
s->data[s->top++] = value;
return 1;
}
static int pop(Stack *s, int *out) {
if (s->top == 0) {
return 0;
}
*out = s->data[--s->top];
return 1;
}
typedef struct {
int data[CAP];
int head;
int size;
} Queue;
static int enqueue(Queue *q, int value) {
if (q->size == CAP) {
return 0;
}
q->data[(q->head + q->size) % CAP] = value;
q->size++;
return 1;
}
static int dequeue(Queue *q, int *out) {
if (q->size == 0) {
return 0;
}
*out = q->data[q->head];
q->head = (q->head + 1) % CAP;
q->size--;
return 1;
}
int main(void) {
Stack s = {{0}, 0};
for (int v = 10; v <= 40; v += 10) {
push(&s, v);
}
printf("fifth push succeeded: %d\n", push(&s, 50));
int a, b;
pop(&s, &a);
pop(&s, &b);
printf("popped %d then %d\n", a, b);
Queue q = {{0}, 0, 0};
for (int v = 1; v <= 4; v++) {
enqueue(&q, v);
}
int x, y;
dequeue(&q, &x);
dequeue(&q, &y);
enqueue(&q, 5);
enqueue(&q, 6);
printf("dequeued %d and %d, then wrapped around; queue holds:", x, y);
int value;
while (dequeue(&q, &value)) {
printf(" %d", value);
}
printf("\n");
return 0;
}fifth push succeeded: 0 popped 40 then 30 dequeued 1 and 2, then wrapped around; queue holds: 3 4 5 6
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
#include <stdio.h> #define CAP 4 typedef struct { int data[CAP]; int top;} Stack; static int push(Stack *s, int value) { if (s->top == CAP) { return 0; } s->data[s->top++] = value; return 1;} static int pop(Stack *s, int *out) { if (s->top == 0) { return 0; } *out = s->data[--s->top]; return 1;} typedef struct { int data[CAP]; int head; int size;} Queue; static int enqueue(Queue *q, int value) { if (q->size == CAP) { return 0; } q->data[(q->head + q->size) % CAP] = value; q->size++; return 1;} static int dequeue(Queue *q, int *out) { if (q->size == 0) { return 0; } *out = q->data[q->head]; q->head = (q->head + 1) % CAP; q->size--; return 1;} int main(void) { Stack s = {{0}, 0}; for (int v = 10; v <= 40; v += 10) { push(&s, v); } printf("fifth push succeeded: %d\n", push(&s, 50)); int a, b; pop(&s, &a); pop(&s, &b); printf("popped %d then %d\n", a, b); Queue q = {{0}, 0, 0}; for (int v = 1; v <= 4; v++) { enqueue(&q, v); } int x, y; dequeue(&q, &x); dequeue(&q, &y); enqueue(&q, 5); enqueue(&q, 6); printf("dequeued %d and %d, then wrapped around; queue holds:", x, y); int value; while (dequeue(&q, &value)) { printf(" %d", value); } printf("\n"); return 0;}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Store at top and move top up by one. | — | — | |
| The array holds 4 elements, so the fifth push fails. | — | fifth push succeeded: 0 | |
| The last values pushed come out first. | — | fifth push succeeded: 0 popped 40 then 30 | |
| The new element goes at the slot after the last one, wrapping around the end of the array. | — | fifth push succeeded: 0 popped 40 then 30 | |
| After two dequeues the slots at the front are free, so 5 and 6 wrap around into them. | queue data wraps | fifth push succeeded: 0 popped 40 then 30 | |
| The queue still returns items in first-in, first-out order. | queue data wraps | fifth push succeeded: 0 popped 40 then 30 dequeued 1 and 2, then wrapped around; queue holds: 3 4 5 6 |
Behind the scenes
How C processes this code
The C compiler produces a native program. Variables occupy memory, and execution enters main.
Cost and common mistakes
push, pop, enqueue, and dequeue are all O(1).
- Ignoring the return value of push or enqueue silently loses data when the structure is full.
- Using only head and tail indexes makes a full queue look empty. Keeping a size field avoids the ambiguity.
- For a growing structure, use malloc and realloc, or a linked list, instead of a fixed capacity.
Try it yourself
What does the stack hold after the two pops?
Reveal answer and reasoning
The values 10 and 20, with 20 on top.