Turn stock data into useful decisions
A small store must know what it owns, what can be sold and what must be reordered. A simple quantity variable is insufficient because every product has its own identity, price and safe-stock threshold. This case study models each product as a dynamically allocated node and derives operational reports from the current list.
Transaction
Receive or sell a positive number of units.
Control
Reject duplicate IDs, negative values and sales above available stock.
Decision
Flag quantity ≤ reorder level and calculate inventory value.
Convert real-world policy into conditions
| Rule | Condition | System response |
|---|---|---|
| Unique identity | No existing node has the same product ID | Reject duplicate Add |
| Valid master data | ID > 0; text not empty; numeric fields ≥ 0 | Do not allocate a node |
| Valid transaction | Units > 0 | Reject zero/negative movement |
| No negative stock | sale units ≤ current quantity | Reject impossible sale |
| Reorder alert | quantity ≤ reorderLevel | Show REORDER REQUIRED |
| Valuation | value = quantity × unitPrice | Sum across displayed products |
A self-referential Product structure
Product = { id, name, quantity, reorderLevel, unitPrice, *next }The first five members describe the product. next connects one dynamically allocated record to another. The variable inventory stores only the address of the first node.
inventory
qty 12
qty 4
qty 8
Why keep IDs sorted?
Sorted insertion gives reports a stable, readable order without calling a separate sort. Search remains O(n), but deterministic output helps testing and auditing.
Ownership rules prevent leaks
Allocation ownership
newProduct allocates one node. After successful insertion, the list owns it. If insertion fails, the caller frees it.
Shutdown ownership
freeInventory walks the list, remembers next, frees current, then advances.
Pointer-to-pointer insertion
Product **current represents “the link that must change”. It may be the head pointer or a node’s next field. This removes the usual special case for insertion at the beginning.
while (*current != NULL && (*current)->id < item->id)
current = &(*current)->next;
item->next = *current;
*current = item;Operations and state transitions
Record a sale
- Read product ID and positive unit count.
- Traverse until the ID matches or the list ends.
- Reject if the product is absent or requested units exceed quantity.
- Subtract units and calculate transaction value.
- Compare new quantity with the reorder threshold.
Generate reorder report
- Begin at head and initialise displayed count to zero.
- For each node, test
quantity <= reorderLevel. - Print matching nodes and accumulate their current stock value.
- Report “No products need reordering” if count remains zero.
Persistence cycle
Compiler-ready C11 program
This implementation uses a human-readable pipe-delimited text file. Names may contain spaces, but the pipe character is reserved as the field separator.
Loading source…Trace: add two products and sell stock
- Program begins with an empty list.
- First product data passes validation.
- Product 205 is allocated.
- 205 becomes the head.
- Second product is created.
- Sorted insertion changes the head.
- Sale targets Mouse.
- Stock is safely reduced.
- Boundary threshold triggers alert.
- Exit persists and cleans memory.
Press Next to begin.
Protect every business rule
Test 1 — Sorted insertion
Test 2 — Duplicate product
Test 3 — Exact-stock sale
Test 4 — Insufficient stock
Test 5 — Threshold boundary
Test 6 — Persistence round trip
Performance of the linked-list design
| Operation | Time | Extra RAM | Trade-off |
|---|---|---|---|
| Find product | O(n) | O(1) | No random access |
| Sorted insertion | O(n) | O(1) | Reports stay ordered |
| Receive/sell stock | O(n) | O(1) | Search dominates |
| Full/reorder report | O(n) | O(1) | Every node may be checked |
| Load or save | O(n) | O(n) | All nodes live in memory |
| Free list | O(n) | O(1) | Every allocation released once |
Test your reasoning
Why does addProduct receive Product **head?
Which condition correctly triggers reordering?
Build the next version
- Add product removal and prove that head/middle/tail deletion all work.
- Record every stock movement in a separate transaction log.
- Add category and supplier fields and generate category-wise value.
- Calculate recommended order quantity from maximum stock and current quantity.
- Replace linear lookup with a hash index while retaining the ordered list for reports.
Questions your project should answer
Why choose a linked list?
The number of products is unknown at compile time. Nodes can be added without resizing a contiguous array, and pointer relinking supports insertion/removal without shifting records.
What causes a memory leak here?
Losing the only pointer to an allocated node or exiting without traversing and freeing the list. The ownership rules and freeInventory prevent both.
Why is fscanf with unrestricted %s unsafe?
It can overflow an array and stops at spaces. The program reads bounded lines and uses a width-limited scanset while loading the delimiter-based file.
How do you preserve list order?
insertSorted walks pointer links until the first larger ID, connects the new node to that position and then redirects the preceding link to the new node.
How would two cashiers use this safely?
This version cannot safely handle concurrent writers. A real system needs transactions, file/database locking and conflict handling, preferably through a database service.
Data structures become valuable through business rules
The linked list is not the final goal. It is the mechanism that supports unknown inventory size. The real system emerges when pointer correctness is combined with validation, stock invariants, reports, persistence and complete memory cleanup.
