Reverse Traversal (Displaying in Reverse)

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.

#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next;
};

void print_reverse_recursive(struct Node *p)
{
    if (p == NULL)
        return;
    print_reverse_recursive(p->next);
    printf("%d ", p->data);
}

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_recursive(n1);   /* output: 30 20 10 */
    printf("\n");

    free(n1); free(n2); free(n3);
    return 0;
}

Program output

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
10print_reverse_recursive(n1)n1 (0x1000)Check p != NULL""
20print_reverse_recursive(n1)n1Call print_reverse_recursive(n2)""
31print_reverse_recursive(n2)n2 (0x2000)Check p != NULL""
41print_reverse_recursive(n2)n2Call print_reverse_recursive(n3)""
52print_reverse_recursive(n3)n3 (0x3000)Check p != NULL""
62print_reverse_recursive(n3)n3Call print_reverse_recursive(NULL)""
73print_reverse_recursive(NULL)NULLBase case: p == NULL""
83print_reverse_recursive(NULL)NULLreturn; (no print)""
92Resume at n3n3Print 30"30 "
102print_reverse_recursive(n3) endsn3Return to n2"30 "
111Resume at n2n2Print 20"30 20 "
121print_reverse_recursive(n2) endsn2Return to n1"30 20 "
130Resume at n1n1Print 10"30 20 10 "
140print_reverse_recursive(n1) endsn1Return to main"30 20 10 "
15—printf("\n") in main—Print newlineline 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)

Call tree for recursive reverse print: recurse to NULL, then printf 30, 20, 10 in post-order
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.

Memory visualization

List nodes (heap)

Address Node data next
0x1000n1100x2000 → n2
0x2000n2200x3000 → n3
0x3000n330NULL

Stack array after push phase (heap)

Index Stored address Points to data
stack[0]0x1000n110
stack[1]0x2000n220
stack[2]0x3000n330

Stack pointer movement

Initial:  top = 0     [   ?   ,   ?   ,   ?   ]
Push n1:  top = 1     [  n1   ,   ?   ,   ?   ]
Push n2:  top = 2     [  n1   ,  n2   ,   ?   ]
Push n3:  top = 3     [  n1   ,  n2   ,  n3   ]
Pop:      top = 2     print stack[2] → 30
Pop:      top = 1     print stack[1] → 20
Pop:      top = 0     print stack[0] → 10

Complexity

Metric Value Notes
Time O(n) 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.