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++.