Criticize my Linked List implementation I'd appreciate any and all criticism about this code.  I didn't have a particular use case in mind when I wrote it; I just wanted to try implementing linked lists.  My comments are not very useful, I think.  I'm not a very experienced programmer, so there's probably plenty of things I could improve or fix here.

Here is the header file:

// llist.h
#ifndef LLIST_H_
#define LLIST_H_

struct LList;
typedef struct LList LList;

struct LLNode;
typedef struct LLNode LLNode;

struct LList {
    LLNode *front, *back;
};

struct LLNode {
    void *data;
    LLNode *next;
};

LList* llist_new(LList **self);
void llist_add(LList *self, void *v);
void llist_remove(LList *self, LLNode *node);
void llist_insert(LLNode *node, void *v);
void llist_destroy(LList *self);

#endif


Here is the .c file:

// llist.c
#include &lt;assert.h&gt;
#include &lt;stdlib.h&gt;

#include "llist.h"

// Initialize a new, empty LList, in dynamically allocated memory.
LList *llist_new(LList **self) {
    *self = malloc(sizeof (LList));
    if (self == NULL) { return NULL; }

    (*self)-&gt;front = NULL;
    (*self)-&gt;back = NULL;

    return *self;
}


// Append a value to the LList.  Creates a new node.
void llist_add(LList *self, void *v) {
    LLNode *node = malloc(sizeof (LLNode));
    assert(node != NULL);

    node-&gt;data = v;
    node-&gt;next = NULL;

    // Put this node at the back of the list.
    if (self-&gt;front != NULL) {
        self-&gt;back-&gt;next = node;
        self-&gt;back = node;
    } else {
        self-&gt;back = node;
        self-&gt;front = node;
    }
}


// Iterate over the LList to locate a node, and then remove it from the list.
void llist_remove(LList *self, LLNode *node) {
    LLNode *iter_node = self-&gt;front;
    LLNode *prev_node = NULL;

    while (iter_node != NULL) {
        if (iter_node == node) {
            if (prev_node != NULL) { prev_node-&gt;next = iter_node-&gt;next; }
            else { self-&gt;front = NULL; }
            free(iter_node);
            return;
        }
        prev_node = iter_node;
        iter_node = iter_node-&gt;next;
    }

}


// Insert a value after a particular node.
void llist_insert(LLNode *node, void *v) {
    LLNode *next = node-&gt;next;
    LLNode *new_node = malloc(sizeof (LLNode));

    new_node-&gt;data = v;
    new_node-&gt;next = next;

    node-&gt;next = new_node;
}


void llist_destroy(LList *self) {
    LLNode *iter_node = self-&gt;front;

    while (iter_node != NULL) {
        LLNode *next = iter_node-&gt;next;
        free(iter_node);
        iter_node = next;
    }

    free(self);
}
// Initialize a new, empty LList, in dynamically allocated memory.
LList *llist_new(LList **self) {


Its odd to return the new list as a return value and by reference.

    *self = malloc(sizeof (LList));
    if (self == NULL) { return NULL; }


Doing this check after you've already dereferenced self seems kinda pointless. Maybe you meant *self?

    (*self)-&gt;front = NULL;
    (*self)-&gt;back = NULL;

    return *self;
}


// Append a value to the LList.  Creates a new node.
void llist_add(LList *self, void *v) {
    LLNode *node = malloc(sizeof (LLNode));
    assert(node != NULL);

    node-&gt;data = v;
    node-&gt;next = NULL;

    // Put this node at the back of the list.
    if (self-&gt;front != NULL) {


I'd add a comment to explain what having self->front NULL means.

        self-&gt;back-&gt;next = node;
        self-&gt;back = node;
    } else {
        self-&gt;back = node;
        self-&gt;front = node;
    }
}


// Iterate over the LList to locate a node, and then remove it from the list.
void llist_remove(LList *self, LLNode *node) {
    LLNode *iter_node = self-&gt;front;
    LLNode *prev_node = NULL;

    while (iter_node != NULL) {
        if (iter_node == node) {
            if (prev_node != NULL) { prev_node-&gt;next = iter_node-&gt;next; }
            else { self-&gt;front = NULL; }


I think putting the blocks on the same line like this makes it harder to read. I also think it fail if you delete the first node. 

            free(iter_node);
            return;
        }
        prev_node = iter_node;
        iter_node = iter_node-&gt;next;
    }

}


// Insert a value after a particular node.
void llist_insert(LLNode *node, void *v) {
    LLNode *next = node-&gt;next;
    LLNode *new_node = malloc(sizeof (LLNode));

    new_node-&gt;data = v;
    new_node-&gt;next = next;


Why did you have the next local variable. It doesn't seem to have helped make the code clearer.

    node-&gt;next = new_node;
}


void llist_destroy(LList *self) {
    LLNode *iter_node = self-&gt;front;

    while (iter_node != NULL) {
        LLNode *next = iter_node-&gt;next;
        free(iter_node);
        iter_node = next;
    }

    free(self);
}