Embedded C

Linked Lists

A linked list is nothing more than a struct holding a pointer to another struct of its own type — the entire data structure is built from that one self-referential idea. Embedded code reaches for arrays far more often than linked lists, for reasons that connect directly back to Dynamic Memory, but the pattern itself shows up constantly once memory pools enter the picture.

Quick reference
What it is
A chain of nodes, each holding data plus a pointer to the next (and sometimes previous) node.
Mental model
A scavenger hunt — each clue (node) only tells you where the next one is, and you can't skip ahead without following the chain from wherever you're currently standing.
Key numbers
O(1) insert/delete at a known position; O(n) to reach an arbitrary element by index — the opposite trade-off from an array.
Reach for it when
The maximum size genuinely isn't known ahead of time, or elements need to be inserted/removed from the middle frequently — otherwise, an array wins on both simplicity and embedded-friendliness.

The self-referential node

struct node {
    int data;
    struct node *next;   // a struct pointing to another struct of its own type
};

Singly linked

Each node points only forward. Traversal is one-directional; inserting at the head is O(1), but inserting anywhere else requires walking the list from the head first.

struct node *push_front(struct node *head, int value) {
    struct node *n = malloc(sizeof(*n));
    n->data = value;
    n->next = head;     // new node points at the old head
    return n;            // caller's head pointer now becomes this node
}

Doubly linked

Each node carries both next and prev, enabling backward traversal and O(1) removal of a node whose address is already known, without needing to find its predecessor first — at the cost of one extra pointer per node and twice as many pointer updates to get right on every insert/delete.

struct dnode {
    int data;
    struct dnode *next;
    struct dnode *prev;
};

Circular

The last node's next points back to the first node instead of NULL — there's no "end" to reach. Useful for anything that logically repeats: a simple round-robin scheduler's task list, or cycling through a fixed set of I/O channels in a loop that never actually terminates.

A singly linked list ends in NULL; a doubly linked list adds backward pointers between the same nodes; a circular list's last node points back to the first instead of terminating Singly linked Node 1 Node 2 Node 3 NULL Doubly linked Node 1 Node 2 Node 3 Circular Node 1 Node 2 Node 3 last node's next points back to the first
Same node struct, three different link patterns — the choice is purely about what the traversal needs to do, not a different data structure underneath.

Linked lists vs. arrays

ArrayLinked list
Random accessO(1)O(n)
Insert/delete at known positionO(n) (shifting)O(1)
Memory layoutContiguous — cache-friendlyScattered — poor cache locality
SizeFixed at compile time (or costly resize)Grows one node at a time
AllocationUsually one static blockUsually one allocation per node

That last row is the one that matters most for embedded: a linked list built with raw malloc() per node inherits every problem from Dynamic Memory — fragmentation, non-deterministic timing, allocation failure. This is why embedded code favors arrays whenever the maximum size is known ahead of time, and reserves linked lists for cases where nodes are drawn from a pre-sized static pool rather than the raw heap.

Real-world applications

Gotchas

Field notes