Welcome to the final article of the self-study C series. By now we have covered variables, functions, pointers, dynamic memory allocation, and how to combine all of that into a linked list, a stack and a queue. Picturing how the pointers actually connect and how the addresses move around while the code runs is still fairly abstract, though. To fix that, I built an interactive data structure visualiser that runs directly in your browser, below.

1. How does visualising a pointer help?

When you insert at the head, insert at the tail, or delete a node, what the machine really does is change the address held in the next pointers:

  • Insert head: a new node is created and its next pointer is set to the old address of head. Then head itself is updated to point straight at this new node.
  • Insert tail: walk from the start of the list to the last node (the one whose next == NULL). Then set that last node's next pointer to the newly created node.
  • Delete head: save the address of the first node in a temporary variable temp. Move head forward to the next node (head = head->next). Finally release the memory of the old first node through the temporary pointer temp.

2. The interactive data structure simulator

Add and remove a few nodes below to watch the simulated RAM and the next pointer connections change in real time.

Log: ready to run an algorithm...

πŸ“₯ Download the simulator's source: ds_visualizer.html

3. What do those three buttons look like in C?

The log line under the simulator is not a vague narration β€” it reads back the exact C lines that would run if you wrote this yourself. Here are all three operations, written out in full so you can compile and run them, and compare each line against the animation above.

linked_list_ops.c
// Insert Head: O(1) - no traversal, only two pointer assignments
void insertAtHead(Node **head, int value) {
    Node *newNode = createNode(value);
    newNode->next = *head;   // The new node points at the old first node
    *head = newNode;         // head now points at the new node
}

// Insert Tail: O(N) - the whole list has to be walked to find the last node
void insertAtTail(Node **head, int value) {
    Node *newNode = createNode(value);
    if (*head == NULL) {     // An empty list: the new node becomes the head
        *head = newNode;
        return;
    }
    Node *cur = *head;
    while (cur->next != NULL) {  // Walk until the node whose next is NULL
        cur = cur->next;
    }
    cur->next = newNode;
}

// Delete Head: O(1) - keep the old head in temp so it can still be freed
void deleteHead(Node **head) {
    if (*head == NULL) {
        printf("The list is empty, nothing to delete\n");
        return;
    }
    Node *temp = *head;      // Remember the address before losing it
    *head = (*head)->next;   // Move head one node forward
    free(temp);              // Only now is it safe to release the old node
}

Two things are worth noticing against the animation. First, insertAtHead and deleteHead contain no loop at all β€” they are O(1), so whether the list holds 3 nodes or 3 million, they take the same time. insertAtTail, by contrast, has to walk the entire list to find the last node, making it O(N): press Insert Tail on a longer list and the journey gets longer (which is also why real implementations usually keep an extra tail pointer, exactly like the Queue in Lesson 10).

Second, look closely at deleteHead: you must save temp = *head before moving head along. Reverse the order β€” move head first and only then free β€” and the old node's address is already gone, with no way left to release it; that is precisely the memory leak described in Lesson 9.

Download the sample source:

linked_list_ops.c is the complete version (with createNode, printList, freeList and main), which runs straight away with gcc -Wall -std=c11 linked_list_ops.c -o demo.

4. Why does learning data structures matter?

Every data structure was designed to be optimal for one particular purpose:

  • Linked list: optimal for inserting and deleting data at any position (O(1) once you already hold the position), and it removes the fixed-size limitation of an array.
  • Stack: extremely effective whenever you need a history to "step back" through (Undo in a word processor, the Back button in a browser, handling recursive calls in the runtime).
  • Queue: optimal for scheduling work in arrival order (a printer's job queue, CPU task scheduling, network packet transmission).

Closing note for the self-study C series:

Thank you for staying with all 12 lessons of this C series, from installing a compiler through to pointers, manual memory management and hand-built data structures. This foundation is the launch pad that makes C++ (classes, inheritance, polymorphism) and JavaScript (asynchrony, the event loop) far easier to pick up in the next series on the js-tools blog.

πŸ“ Final quiz for the series
A C program dynamically allocates a linked list of 1000 nodes on the heap. The programmer, however, forgets to call free() before the program ends (the process stops completely). What happens to that heap memory on a modern operating system (macOS, Linux)?

Related lessons in this series

Lesson 11: Multi-file programming, the preprocessor & build tools in C Back to the C series roadmap (Vietnamese)