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 "
15printf("\n") in mainPrint 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->nextn2
2 n2 Yes 2 t = t->nextn3
3 n3 Yes 3 t = t->nextNULL
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
0x1000n1100x2000n2
0x2000n2200x3000n3
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.