Introduction
Welcome to the final lesson of our module on linked lists. In the preceding lessons, you've mastered the essential mechanics: defining nodes, inserting and deleting them, using double pointers for elegant head modification, and, most recently, ensuring clean memory deallocation. In parallel, you have an inventory system from Module 7 built with a dynamic array. Today, we bring these two threads together.
This lesson is about applying your new knowledge to a practical engineering task: refactoring the game inventory from a dynamic array to a linked list. This isn't just a coding exercise; it's a critical design decision. We will first delve into the "why"—the specific trade-offs between arrays and linked lists—to justify when and why you'd choose one over the other. Then, we will walk through the "how"—the concrete steps to replace the realloc-based array logic with robust linked list operations.
Justifying the Choice: Arrays vs. Linked Lists
At first glance, arrays and linked lists both solve the problem of storing a collection of items. However, their underlying memory layout leads to profoundly different performance characteristics. This is a classic trade-off in computer science, and understanding it is key to building efficient software.

To start, let's get a concise summary of the primary advantages of each data structure.
When to use a linked list over an array/array list?
This highly-rated Stack Overflow answer provides a clear, bulleted list of scenarios where each data structure excels.
Please read the first answer, which begins with the heading "Linked lists are preferable...". As you read, think about our game inventory. Do we need constant-time insertion? Do we need random access?
The key takeaway is that the choice hinges on your primary use case:
- Arrays excel at random access. If you frequently need to get the i-th element, an array's access time is unbeatable.
- Linked lists excel at insertion and deletion. If you are constantly adding and removing elements, especially from the middle of the collection, a linked list's operation (once you have a pointer to the location) avoids the costly shifting of elements required in an array, which is an operation.
However, there's a deeper, more subtle factor at play that is especially important in C: cache locality. Modern CPUs don't fetch memory one byte at a time. They pull in larger "cache lines" from main memory into a much faster, smaller cache. Because array elements are stored contiguously, accessing array[0] often pulls array[1], array[2], etc., into the cache for free. This makes iterating through an array incredibly fast. Linked list nodes, allocated one by one, can be scattered all over the heap, leading to a "cache miss" for almost every node you visit.
This video provides an excellent explanation of these performance trade-offs, including the crucial impact of the CPU cache.
Why Linked Lists vs Arrays isn’t a real choice
In this video, SimonDev gives a fantastic breakdown of the performance differences between arrays and linked lists, with a special focus on the practical implications of cache locality.
Watch the section from the detailed comparison. Pay close attention to his explanation of how the prefetcher and cache misses affect traversal speed. This provides the low-level justification for the performance differences.
So, for our inventory system, what is the right choice?
- Argument for a Linked List: If players frequently pick up and drop items, and the inventory has no fixed size limit, a linked list is very flexible. Adding or removing an item doesn't require resizing a large block of memory. The logic is clean and handles dynamic growth naturally.
- Argument for a Dynamic Array: If the primary interaction is simply listing all items, an array's cache-friendly traversal is a significant performance win. If the inventory size is typically small and doesn't change drastically, the overhead of
reallocmight be negligible, making the array simpler and faster for iteration.
For this project, we will proceed with a linked list. It's a common choice for this kind of game feature and provides an excellent opportunity to apply what you've learned. The flexibility it offers for adding and removing items is a powerful feature for a dynamic game world.
The Refactoring Process: From Array to List
Let's refactor the inventory system. We'll follow a clear, step-by-step process. The "Car Lot" inventory example in the following document provides a great parallel for our task.
These lecture slides from the University of Texas at Austin walk through building a linked-list-based inventory for a car lot, which is conceptually identical to our game item inventory.
Focus on these key sections: Read the introduction and comparison to reinforce the core concepts. Then, review the "Car Lot" example, specifically the carType struct definition, which shows how to create a self-referential struct for an inventory item. Finally, study the logic in the code excerpts for adding and deleting nodes. This will be the blueprint for our addItem and removeItem functions.
Here is the plan for our refactoring:
1. Update the Data Structures
Your inventory is currently managed by a pointer to a block of Item structs, maybe like Item* inventory;, and a head pointer to a LinkedList struct. Let's adjust this.
ItemStruct: YourItemstruct needs to become self-referential. Add a pointer to the next item.// In your item.h or equivalent typedef struct Item { char name[50]; int value; // ... other item properties struct Item *next; // Add this line! } Item;- Inventory Representation: Instead of a dynamic array, your inventory will now be represented by a single pointer to the first item.
// In your player.h or game.h typedef struct Player { // ... other player stats Item* inventory_head; // This is now the head of your item list } Player;
2. Rewrite Inventory Management Functions
The core of the work is replacing the array-based logic with linked list operations.
addItem(Player* player, ...): This function previously might have usedreallocto grow the array. Now, it will:malloca newItemnode.- Populate the new node's data (name, value, etc.).
- Set its
nextpointer toNULL. - Insert the new node into the list. A simple strategy is to insert at the head, which is an operation.

-
removeItem(Player* player, ...): This function previously might have involved shifting elements in the array. Now, it will:- Traverse the list to find the item to be removed. You'll need to keep track of the
previousnode as you traverse. - Once found, "bypass" the node by setting
previous->next = current->next. (Handle the special case where the item to be removed is the head). freethe memory for the removed node.
- Traverse the list to find the item to be removed. You'll need to keep track of the
-
displayInventory(Player* player): This function becomes a straightforward list traversal.- Create a temporary
currentpointer, initialized toplayer->inventory_head. - Loop while
currentis notNULL. - Inside the loop, print the data from the
currentnode. - Move to the next node:
current = current->next.
- Create a temporary
3. Ensure Proper Memory Cleanup
In your main game loop's exit path, you must now clean up the linked list. The previous lesson covered this in detail. You will call your destroy_list function, passing it the address of the inventory_head pointer.
// In main, before exiting
destroy_list(&player.inventory_head);
As always, verify your work with Valgrind. After adding items, removing some, and then quitting the game, Valgrind should report all heap blocks were freed -- no leaks are possible. This is the professional standard for memory management in C.
For a comprehensive C implementation walkthrough of creating, appending to, printing, and freeing a list, you can refer back to Mike Shah's detailed tutorial. It serves as an excellent reference for the complete lifecycle.
Your Fourth Day in C (Building a Linked List Data Structure) - Crash Course in C Programming
This video provides a complete, from-scratch implementation of a linked list in C.
If you need a refresher on the implementation details, these sections are particularly relevant: Appending a node: append logic. Printing the list: print logic. Freeing the entire list and verifying with Valgrind: freeing logic.
Conclusion
You have now completed a significant milestone: not only understanding an advanced data structure but also applying it to refactor an existing piece of code. This process of evaluating trade-offs and modifying code accordingly is at the very heart of software engineering.
Key Takeaways:
- The choice between a dynamic array and a linked list is a trade-off between access patterns: arrays offer random access and superior cache performance for iteration, while linked lists offer insertion/deletion flexibility.
- Refactoring from an array to a linked list involves changing the data structure to be self-referential and replacing array-specific logic (
realloc, index-based access, shifting) with node-based logic (traversal, pointer manipulation,malloc/freeper node). - For a dynamic inventory with frequent additions and removals, a linked list offers a flexible and scalable design, though at the cost of slower traversal compared to a contiguous array.
You are now fully equipped with the tools to manage complex, dynamic collections of data. In our next lesson, we will address the growing complexity of our project itself. As the codebase expands, keeping everything in one file becomes unmanageable. We will learn to organize the project into logical modules with separate .c and .h files and use a Makefile to automate the build process.