Chained hash table implementation While reading a data structure book, I have implemented a chained hash table. I want to share the code with you and get your opinion about it.

chained_hash_table.h

#ifndef CHAINED_HASH_TABLE_H_INCLUDED
#define CHAINED_HASH_TABLE_H_INCLUDED

#include &lt;stdlib.h&gt;
#include "../linked_list/linked_list.h"

typedef size_t hkey_t;

typedef struct ChainedHashTable_ {
    size_t buckets;
    List **table;
    hkey_t (*hash)(const void *key);
    void (*destroy)(void *data);
    int (*compare)(const void *fdata, const void *sdata);
}ChainedHashTable;

ChainedHashTable *chtb_init(size_t buckets,
        hkey_t (*hash)(const void *key),
        void (*destroy)(void *data),
        int (*compare)(const void *fdata, const void *sdata));
void chtb_destroy(ChainedHashTable *chtb);
int chtb_insert(ChainedHashTable *chtb, void *data);
int chtb_remove(ChainedHashTable *chtb, void **data);
int chtb_lookup(const ChainedHashTable *chtb, const void *data);

#endif


chained_hash_table.c

#include &lt;string.h&gt;

#include "chained_hash_table.h"

#define chtb_hash_key(chtb, data)   chtb-&gt;hash(data) % chtb-&gt;buckets

ChainedHashTable *chtb_init(size_t buckets,
        hkey_t (*hash)(const void *key),
        void (*destroy)(void *data),
        int (*compare)(const void *fdata, const void *sdata)) {

    ChainedHashTable *chtb;

    if( (chtb = malloc(sizeof *chtb)) != NULL ) {
        chtb-&gt;buckets = buckets;
        chtb-&gt;hash = hash;
        chtb-&gt;destroy = destroy;
        chtb-&gt;compare = compare;

        if( (chtb-&gt;table = malloc(buckets * sizeof *chtb-&gt;table)) == NULL) {
            free(chtb);
            return NULL;
        }

        int i;

        for(i = 0; i &lt; buckets; ++i) {
            if( (chtb-&gt;table[i] = list_init(destroy, compare)) == NULL) {
                for(--i; i &gt;= 0; --i) 
                    free(chtb-&gt;table[i]);

                free(chtb);
                return NULL;
            }
        }
    }

    return chtb;
}

void chtb_destroy(ChainedHashTable *chtb) {
    size_t i;

    for(i = 0; i &lt; chtb-&gt;buckets; ++i)
        if(chtb-&gt;table[i] != NULL)
            list_destroy(chtb-&gt;table[i]);

    free(chtb-&gt;table);
    free(chtb);
}

int chtb_insert(ChainedHashTable *chtb, void *data) {
    size_t bucket = chtb_hash_key(chtb, data); 

    if(list_find(chtb-&gt;table[bucket], data) != NULL)
        return 1;

    if(list_prepend(chtb-&gt;table[bucket], data) != 0)
        return -1;

    return 0;
}

int chtb_remove(ChainedHashTable *chtb, void **data) {
    size_t bucket = chtb_hash_key(chtb, data); 

    if(chtb-&gt;table[bucket] == NULL)
        return -1;
    return list_remove(chtb-&gt;table[bucket], data);
}

int chtb_lookup(const ChainedHashTable *chtb, const void *data) {
    size_t bucket = chtb_hash_key(chtb, data); 

    return list_find(chtb-&gt;table[bucket], data) == NULL ? 0 : 1;
}
typedef size_t hkey_t; doesn't seem right. Since hash has to be implemented by the client, the client must know exactly what hkey_t is. It would however make certain sense to use hkey_t throughout your code.
It is very suspicious that data argument to remove has an extra level of indirection, compared to insert and lookup. Is it a typo?
I'd recommend to allocate buckets with calloc instead of malloc. Then a cleanup loop of chtb_init can be replaced with a call to chtb_destroy, that is stating your intention explicitly.
Since the linked_list.h code is not posted, it is impossible to reason about the rest of the functions (specifically, return codes). In general, it make sense to return more than just failure code (a pointer to an inserted node, maybe? - cf STL inserts).