Several valid critiques have been offered, but I have not yet seen a design suggestion that stands to greatly decrease the complexity of a common list operation: appending to the end of a list.
Currently you have to traverse the whole list to find the last node, then attach a new node to that. However, since a list is separate from the internal nodes, in addition to a head node, your list can contain a link to the tail node.
With this in mind, the code might look something like:
```
typedef struct Node {
struct Node* next;
int data;
} Node;
typedef struct LinkedList {
Node* head;
Node* tail;
int length;
} LinkedList;
LinkedList* append(LinkedList* list, int data) {
// Checks on validity of list and its pointers.
Node* new_node = malloc(sizeof Node);
if (!new_node) return NULL;
new_node->next = NULL;
new_node->data = data;
list->tail->next = new_node;
list->tail = new_node;
list->length++;
return list;
}
```
Appending now becomes a O(1) operation rather than O(n), and helps you avoid the fact that `_add_node`, which `append` calls is recursive rather than iterative. Now, it's _tail_-recursive, which is good, but you can't count on C compilers to optimize for this, and you're opening yourself up to the possibility of a stack overflow on a long list.
Also consider whether your efforts are well-spent implementing a linked list in the first place. Search for a video from Bjarne Stroustrop on linked lists vs. dynamic arrays in C++. The basic message is relevant to C as well as C++.