A singly linked list (SLL) only has next, so you cannot walk backward in one forward pass. To print values in reverse order without reversing the list structure, common options are: recurse first and print on the way back (implicit call stack), or push node pointers onto an explicit stack then pop. Both are O(n) time; recursion uses O(n) call-stack space; the array stack uses O(n) extra space for pointers. (A doubly linked list could use prev instead.)
1) Recursion — print after visiting next
Recurse to the end of the list first; print when unwinding. Output order becomes reverse of the forward chain.
Reverse order on one line, then newline from main:
30 20 10
Recursive call stack (post-order: recurse first, print on unwind)
Call #
p points to
p == NULL?
Action
When print runs
Output
1
n1 (data = 10)
No
Call print_reverse_recursive(n1->next) first
After deeper calls return
(delayed)
2
n2 (data = 20)
No
Call print_reverse_recursive(n2->next) first
After deeper calls return
(delayed)
3
n3 (data = 30)
No
Call print_reverse_recursive(n3->next) first
After deeper calls return
(delayed)
4
NULL
Yes
return; (base case)
No print
(none)
3′
n3 resumes
—
printf("%d ", p->data)
Now
30
2′
n2 resumes
—
printf("%d ", p->data)
Now
20
1′
n1 resumes
—
printf("%d ", p->data)
Now
10
Complete call stack trace (with unwinding)
Step
Depth
Function / phase
p value
Operation
Accumulated output
1
0
print_reverse_recursive(n1)
n1 (0x1000)
Check p != NULL
""
2
0
print_reverse_recursive(n1)
n1
Call print_reverse_recursive(n2)
""
3
1
print_reverse_recursive(n2)
n2 (0x2000)
Check p != NULL
""
4
1
print_reverse_recursive(n2)
n2
Call print_reverse_recursive(n3)
""
5
2
print_reverse_recursive(n3)
n3 (0x3000)
Check p != NULL
""
6
2
print_reverse_recursive(n3)
n3
Call print_reverse_recursive(NULL)
""
7
3
print_reverse_recursive(NULL)
NULL
Base case: p == NULL
""
8
3
print_reverse_recursive(NULL)
NULL
return; (no print)
""
9
2
Resume at n3
n3
Print 30
"30 "
10
2
print_reverse_recursive(n3) ends
n3
Return to n2
"30 "
11
1
Resume at n2
n2
Print 20
"30 20 "
12
1
print_reverse_recursive(n2) ends
n2
Return to n1
"30 20 "
13
0
Resume at n1
n1
Print 10
"30 20 10 "
14
0
print_reverse_recursive(n1) ends
n1
Return to main
"30 20 10 "
15
—
printf("\n") in main
—
Print newline
line ends
Memory layout (same three-node list)
Node
Address
data
next
Points to
n1
0x1000
10
0x2000
n2
n2
0x2000
20
0x3000
n3
n3
0x3000
30
NULL (0x0)
nowhere
Visual call tree (post-order printing)
Recursive calls unwind before printf: output order 30 20 10.
Timing: when prints occur
Phase
t1
t2
t3
t4
t5
t6
t7
Call stack (concept)
rev(n1)
rev(n1)→rev(n2)
…rev(n3)
…rev(NULL)
unwind n3
unwind n2
unwind n1
Action
Recurse down
Recurse down
Recurse down
Base case
Print 30
Print 20
Print 10
Output so far
""
""
""
""
"30 "
"30 20 "
"30 20 10 "
Forward vs reverse recursive printing
Aspect
Forward (display_recursive)
Reverse (print_reverse_recursive)
Print position
Before recursive call (pre-order)
After recursive call (post-order for this print)
Output order
10 → 20 → 30
30 → 20 → 10
Stack when printing starts
Still growing (deepest not reached)
Unwinding (deepest already handled)
Base case
if (p == NULL) return;
Same
When values print
On the way “down” the recursion
On the way “up” (after returns)
2) Explicit stack — array of struct Node *
First pass: count nodes (or push in one pass if you pre-allocate). Second pass: fill a stack array with pointers in forward order, then pop from the top while printing. Here we count, then malloc an array of that many pointers.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
void print_reverse_explicit_stack(struct Node *head)
{
int n = 0;
struct Node *t;
for (t = head; t != NULL; t = t->next)
n++;
if (n == 0)
return;
struct Node **stack = malloc((size_t)n * sizeof(struct Node *));
if (stack == NULL)
return;
int top = 0;
for (t = head; t != NULL; t = t->next)
stack[top++] = t;
while (top > 0)
printf("%d ", stack[--top]->data);
printf("\n");
free(stack);
}
int main(void)
{
struct Node *n1 = malloc(sizeof(struct Node));
struct Node *n2 = malloc(sizeof(struct Node));
struct Node *n3 = malloc(sizeof(struct Node));
if (!n1 || !n2 || !n3) return 1;
n1->data = 10; n1->next = n2;
n2->data = 20; n2->next = n3;
n3->data = 30; n3->next = NULL;
print_reverse_explicit_stack(n1); /* output: 30 20 10 */
free(n1); free(n2); free(n3);
return 0;
}
Program output
Same reverse line as the recursive version, then newline from printf("\n") inside the function:
30 20 10
Algorithm phases
Phase
Operation
Variables / effect
Result
1. Count nodes
Walk the list with t
n = 3
Stack length known
2. Allocate stack
malloc(n * sizeof(struct Node *))
stack points to array of 3 pointers
Room for all node addresses
3. Push phase
Second forward walk: stack[top++] = t
stack[0]=n1, stack[1]=n2, stack[2]=n3, top = 3
Forward order in the array
4. Pop phase
while (top > 0) printf(..., stack[--top]->data)
Pop n3, then n2, then n1
Prints 30 20 10
5. Cleanup
free(stack)
Heap block released
No leak from the stack array
Phase 1: counting nodes
Step
t points to
t != NULL?
n after step
Action
Start
head (n1)
—
0
n = 0, enter loop
1
n1
Yes
1
t = t->next → n2
2
n2
Yes
2
t = t->next → n3
3
n3
Yes
3
t = t->next → NULL
4
NULL
No
3
Exit loop; n = 3
Result:n = 3 nodes.
Phase 2: memory allocation
Variable
Value (example)
Description
stack
Address (e.g. 0x5000)
Start of heap block for n pointers
stack[0]…stack[2]
Uninitialized until push
Will hold n1, n2, n3 addresses
top
0 (before push loop)
Next free slot index / length after fills
Phase 3: pushing node pointers
Iteration
t points to
Assignment
top after
Stack slots (concept)
Start
—
—
0
[ ?, ?, ? ]
1
n1 (0x1000)
stack[0] = n1
1
[ n1, ?, ? ]
2
n2 (0x2000)
stack[1] = n2
2
[ n1, n2, ? ]
3
n3 (0x3000)
stack[2] = n3
3
[ n1, n2, n3 ]
After the push loop, top == 3 (number of elements stored). The pop loop uses while (top > 0) with stack[--top].
Phase 4: popping and printing
Step
top before
After --top
Index used
Node
data
Output so far
Start
3
—
—
—
—
""
1
3
2
stack[2]
n3
30
"30 "
2
2
1
stack[1]
n2
20
"30 20 "
3
1
0
stack[0]
n1
10
"30 20 10 "
4
0
—
—
—
—
Loop exits
Then printf("\n") appends the newline. List nodes are still freed later in main.
Count pass + push pass + pop pass each touch n work
Extra space
O(n)
Array of n pointers
Count pass
O(n)
One list traversal
Push pass
O(n)
Second list traversal
Pop pass
O(n)
n decrements / prints
Pointer size
Typically 8 bytes (64-bit)
Each slot in stack is one pointer
Explicit stack vs recursion
Aspect
Explicit stack
Recursion
Where “stack” lives
Heap (malloc array)
Call stack (activation records)
Control
You size/free the buffer explicitly
Compiler manages frame size/limits
Very large n
Limited mainly by heap; no stack overflow from recursion depth
Deep recursion can overflow the call stack
Code size
More steps (count, alloc, push, pop, free)
Short and clear
Call overhead
No per-node function calls in the walk (only loops)
One call per node for the recursive version
Portability
Standard malloc/free
Depends on platform stack limit
The stack array holds pointers to existing nodes; it does not copy the list. If you cannot afford two passes, you can push pointers in a single traversal into a dynamically grown buffer, or use a linked stack—same idea.
Comparison: Reverse linked list vs tree post-order traversal
The fundamental similarity
Both follow the “process after children” pattern:
Concept
Reverse linked list
Tree post-order
Pattern
Process node after recursive call
Process node after children
Order
Left → right → print
Left → right → root
Analogy
“Print next, then print me”
“Print children, then print parent”
Key similarities
Aspect
Reverse linked list
Tree post-order
Traversal type
Depth-first
Depth-first
Processing order
After “child” (next)
After children
Base case
p == NULL
root == NULL
Recursive calls
1 call (to next)
2 calls (left, right) for binary tree
Print position
After recursive call
After recursive calls
Stack usage
O(n)
O(h) where h = height
Natural order
Reverse of list
Left-right-root
Practical examples where both are useful
Application
Reverse linked list
Tree post-order
Expression evaluation
N/A (linear)
Evaluate mathematical expressions
Undo / redo operations
Reverse history
Hierarchical undo
File system traversal
List files backwards
Calculate directory sizes
Dependency resolution
Reverse dependency chain
Build dependency trees
Syntax analysis
N/A
Parse tree evaluation
Printing a linked list in reverse order is essentially a special case of post-order traversal where:
Each node has exactly one “child” (next pointer).
The post action is printing the node.
The linear structure simplifies to: go to the end, then print while coming back.