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
nextpointer is set to the old address ofhead. Thenheaditself 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'snextpointer to the newly created node. -
Delete head: save the address of the first node in a temporary variable
temp. Moveheadforward to the next node (head = head->next). Finally release the memory of the old first node through the temporary pointertemp.
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.
π₯ 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.
// 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.
free() before the program ends (the process stops completely). What
happens to that heap memory on a modern operating system (macOS, Linux)?
Comments