Linked List Operations in C Read Proof I am trying to write up all the linked list basic operations (push, pop, add_at_end, pop_from_end, add_at_index, pop_from_index). This is not a school assignment, even though it may look like one. I have written the code. I have tested it myself quite a bit, although I am no C guru. I would like it if you could tell me if there are any suggestions / corrections you have for the code in terms of efficiency, memory handling, readability, clarity etc.

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

typedef struct node {
    int data;
    struct node * next;
} node_t;

void print_list(node_t * head);
void push(node_t ** head, int data);
int pop(node_t ** head);
int add_at_end(node_t ** head, int data);
int remove_last(node_t * head);
int add_at_index(node_t ** head, int n, int data);
int remove_by_index(node_t ** head, int n);


int main()
{
    node_t * head = NULL;

    head = malloc(sizeof(node_t));
    if (head == NULL) {
      return 1;
    }
    head-&gt;data = 1;
    head-&gt;next = NULL;
    print_list(head);
    return 0;
}

void push(node_t ** head, int data) {
    node_t * new_node;
    new_node = malloc(sizeof(node_t));
    new_node-&gt;data = data;
    new_node-&gt;next = *head;
    *head = new_node;
}

void print_list(node_t * head) {
    node_t * current = head;

    while (current != NULL) {
        printf("%d\n", current-&gt;data);
        current = current-&gt;next;
    }
}

int add_at_end(node_t ** head, int data) {
    node_t * new_node = malloc(sizeof(node_t));
    new_node-&gt;data = data;
    new_node-&gt;next=NULL;
    if(*head==NULL){
        *head = new_node;
        return 1;
    }
    node_t * current = *head;
    while (current-&gt;next != NULL) {
        current = current-&gt;next;
    }
    current-&gt;next = new_node;
    return 1;
}

int add_at_index(node_t ** head, int n, int data) {
    node_t * current = *head;
    node_t * temp_node = NULL;
    if(n == 0 || *head==NULL){
        push(head, data);
        return 1;
    }
    while(n&gt;1 &amp;&amp; current-&gt;next!=NULL){
        current = current-&gt;next;
        n--;
    }
    if(n&gt;1){
        return -1;
    }

    temp_node = malloc(sizeof(node_t));
    temp_node-&gt;data = data;
    temp_node-&gt;next = current-&gt;next;
    current-&gt;next = temp_node;
    return 1;
}

int remove_by_index(node_t ** head, int n) {
    node_t * current = *head;
    node_t * curr_head = NULL;
    if(n == 0||*head==NULL){
        pop(head);
        return 1;
    }
    while(n&gt;0 &amp;&amp; current-&gt;next!=NULL){
        curr_head = current;
        current = current-&gt;next;
        n--;
    }
    if(n&gt;0){
        return -1;
    }
    curr_head-&gt;next = current-&gt;next;
    free(current);
    return 1;
}


int pop(node_t ** head) {
    int retval = -1;
    if (*head == NULL) {
        return -1;
    }
    if ((*head)-&gt;next == NULL) {
        printf("here");
        retval = (*head)-&gt;data;
        free(*head);
        *head = NULL;
        return retval;
    }
    node_t * next_node = NULL;
    next_node = (*head)-&gt;next;
    retval = (*head)-&gt;data;
    free(*head);
    *head = next_node;
    return retval;
}

int remove_last(node_t * head) {
    int retval = 0;
    if (head-&gt;next == NULL) {
        head-&gt;data;
        free(head);
        head = NULL;
        return retval;
    }

    node_t * current = head;

    while (current-&gt;next-&gt;next != NULL) {
        current = current-&gt;next;
    }
    retval = current-&gt;next-&gt;data;
    free(current-&gt;next);
    current-&gt;next = NULL;
    return retval;
}
1. You can add some comments !

2. In my opinion you should create another struct that will be named like linked_list_t which will have two pointers, node_t * start ,node_t* end.In terms of efficiency, by doing this, you don't have to iterate all the list on add_at_end to add a single node (this is a common practice when you create a linked_list)

3. When you malloc you can check the returned pointer and if it is NULL you should perror() the message.

4. The code 

typedef struct node {
    int data;
    struct node * next;
} node_t;

void print_list(node_t * head);
void push(node_t ** head, int data);
int pop(node_t ** head);
int add_at_end(node_t ** head, int data);
int remove_last(node_t * head);
int add_at_index(node_t ** head, int n, int data);
int remove_by_index(node_t ** head, int n);


should be put on a header file as it just contains declarations 

5. Use Sentinel Nodes in order not to iterate on int remove_last(node_t *)