Skip to content

Chapter 18 of 21

Dynamic Data Structures

Build linked lists, stacks, and queues with nodes and explicit ownership.

42 minutes 10 quick checksBy Subha Prasad
Lesson 18 of 21Course navigation

Lesson content

Read, practise, then check your understanding

Dynamic structures allocate nodes as data arrives. They trade the cache locality and constant-time indexing of arrays for flexible growth and efficient relinking. Every implementation needs an explicit invariant and ownership policy.

Singly linked list

struct Node {
    int value;
    struct Node *next;
};

int push_front(struct Node **head, int value) {
    struct Node *node = malloc(sizeof *node);
    if (!node) return 0;
    node->value = value;
    node->next = *head;
    *head = node;
    return 1;
}

Insertion at the head is O(1). Searching and indexing are O(n). The pointer-to-pointer lets the function replace the caller’s head.

Release the complete list without using a freed node:

void list_destroy(struct Node **head) {
    struct Node *current = *head;
    while (current) {
        struct Node *next = current->next;
        free(current);
        current = next;
    }
    *head = NULL;
}

A doubly linked list adds previous links for O(1) removal when a node is already known, at the cost of memory and more invariants.

Stack

A stack follows last-in, first-out order. A linked stack can push and pop at the head in O(1). An array-backed stack is often faster because of locality and fewer allocations; grow it geometrically with checked realloc.

Queue

A linked queue maintains front and rear pointers so enqueue and dequeue are O(1).

struct Queue {
    struct Node *front;
    struct Node *rear;
};

Invariant: both pointers are null for an empty queue; otherwise rear->next is null. Dequeuing the last node must set both pointers to null. A circular array is another strong queue representation when capacity can grow in blocks.

API design and testing

Hide node details behind a module when callers should not manipulate links. Define whether the container owns stored payloads, whether duplicates are allowed, and how allocation failure is reported.

Test empty, one-node, many-node, insertion/removal boundaries, allocation failures, and full destruction. Use sanitizers to catch leaks and invalid links. Big-O describes growth; real performance also depends on allocation cost and cache behavior.

Knowledge check

Answer every question correctly to complete this chapter.

Which structure follows first-in, first-out order?
What must happen to every dynamically allocated list node eventually?
What is the complexity of inserting at a singly linked list head?
Why does push_front often accept struct Node **head?
Which order does a stack follow?
What pointers make linked queue enqueue and dequeue O(1)?
After dequeuing the final queue node, what must happen?
Why can an array-backed stack outperform a linked stack?
What should list destruction save before freeing the current node?
What must a container API define for pointer payloads?

0 of 10 checks passed

Your progress is saved on this device.