Skip to content
Home » All Posts » How to Use uthash.h to Build Blazing-Fast Hash Tables in C

How to Use uthash.h to Build Blazing-Fast Hash Tables in C

Introduction

When you need raw speed and absolute control, plain C stands tall as the king. However, writing your own data structures often feels like reinventing the wheel and drains time away from what truly matters. That’s exactly where uthash.h comes in. This lightweight, single-header C hash table library empowers you to build blazing-fast hash tables with almost no overhead. Instead of wrestling with boilerplate, you can channel your energy directly into performance and problem-solving. In this guide, we’ll dive into how uthash.h works, explain why it remains a hidden gem for C developers, and show you how to harness its power to craft ultra-efficient hash tables for your own projects.

How uthash.h Works

At its core, uthash.h injects hash table functionality directly into your structs through clever preprocessor macros. You add a UT_hash_handle field, and immediately that struct can be stored, searched, and iterated like a complete hash map. Instead of forcing you to deal with hashing, collision resolution, and resizing yourself, uthash.h takes care of everything under the hood. As a developer, you only define your key, call a few intuitive macros (HASH_ADD, HASH_FIND, HASH_DEL), and let uthash.h deliver near-native C speed without the usual complexity.

Why It’s a Hidden Gem for C Developers

High-level languages ship with built-in data structures, but C often leaves you wiring them from scratch. Here’s where uthash.h changes the game. It delivers powerful hash table capabilities in a single header file while preserving the low-level efficiency C developers crave. Because it’s small, portable, and free of unnecessary abstractions, uthash.h slips seamlessly into your codebase without bloat. If you value the raw speed of C but also want the convenience of a ready-to-use hash map, uthash.h offers that rare combination of simplicity, performance, and elegance—making it a genuine hidden gem in the C ecosystem.

Get uthash Source Code

You can download the source code here

Examples

Integer Key C Hash Table Example

To build a hash table keyed by integers, you define a struct that includes your fields (e.g., id and name) and always end it with UT_hash_handle hh. This field must be the last member because uthash uses it to manage the internal hash bookkeeping. You can then add entries with HASH_ADD_INT, search with HASH_FIND_INT, update by modifying the struct after lookup, and remove entries with HASH_DEL. This pattern makes it trivial to map numeric IDs (like user IDs or record IDs) to data with blazing speed.

// int_key.c
#include <stdio.h>
#include <stdlib.h>
#include "uthash.h"

typedef struct {
    int id;                 // key
    const char *name;       // payload
    UT_hash_handle hh;      // makes this struct hashable
} user_t;

int main(void) {
    user_t *users = NULL, *u, *tmp;

    // INSERT
    int ids[] = {42, 7, 99};
    const char *names[] = {"Alice", "Bob", "Carol"};
    for (int i = 0; i < 3; i++) {
        u = malloc(sizeof(*u));
        u->id = ids[i];
        u->name = names[i];                 // (own/copy if needed)
        HASH_ADD_INT(users, id, u);         // key field: id
    }

    // FIND
    int key = 7;
    HASH_FIND_INT(users, &key, u);
    if (u) printf("Found id=%d name=%s\n", u->id, u->name);

    // ITERATE
    for (u = users; u != NULL; u = (user_t*)u->hh.next) {
        printf("Entry: id=%d name=%s\n", u->id, u->name);
    }

    // DELETE ALL
    HASH_ITER(hh, users, u, tmp) {
        HASH_DEL(users, u);
        free(u);
    }
    return 0;
}

String Key C Hash Table Example

For string keys, you follow the same structure: your struct contains the key field (e.g., char *email) along with other payload data, and ends with UT_hash_handle hh. Since uthash doesn’t manage string memory, you must allocate or duplicate the string yourself (commonly with strdup). Use HASH_ADD_STR (or HASH_ADD_KEYPTR if you want explicit control) to insert, HASH_FIND_STR to look up entries, and HASH_DEL to remove them. After lookup, you can directly update any field in the struct to modify the stored value. This approach is ideal for mapping names, emails, or file paths to associated data.

// str_key.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "uthash.h"

typedef struct {
    char *email;            // key (string)
    int  score;             // payload
    UT_hash_handle hh;
} acct_t;

int main(void) {
    acct_t *accts = NULL, *a, *tmp;

    // INSERT (duplicate the string to own it)
    const char *emails[] = {"alice@example.com", "bob@foo.io", "carol@bar.dev"};
    int scores[] = {10, 20, 30};
    for (int i = 0; i < 3; i++) {
        a = malloc(sizeof(*a));
        a->email = strdup(emails[i]);       // uthash doesn’t manage string memory
        a->score = scores[i];
        HASH_ADD_KEYPTR(hh, accts, a->email, strlen(a->email), a); // OR HASH_ADD_STR(accts, email, a)
    }

    // FIND
    const char *key = "bob@foo.io";
    HASH_FIND_STR(accts, key, a);
    if (a) printf("Found %s score=%d\n", a->email, a->score);

    // UPDATE
    if (a) a->score += 5;

    // CLEANUP
    HASH_ITER(hh, accts, a, tmp) {
        HASH_DEL(accts, a);
        free(a->email);
        free(a);
    }
    return 0;
}

Struct (Composite) Key C Hash Table Example

When your key spans multiple fields, such as (tenant_id, user_id), you create a small composite struct to hold the key. Your main data struct embeds that key and ends with UT_hash_handle hh. Since uthash compares keys as raw bytes, you should memset the key struct to zero before assigning values to avoid padding issues. Then, use HASH_ADD with the address and size of the key struct to insert entries, HASH_FIND to search, and HASH_DEL to delete. Updates are straightforward: after HASH_FIND returns a match, you can change the payload field (e.g., role) directly. This pattern lets you use multiple attributes together as a unique identifier without extra code overhead.

// struct_key.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "uthash.h"

typedef struct {
    int  tenant_id;
    int  user_id;
} compkey_t;

typedef struct {
    compkey_t key;          // composite key
    char *role;             // payload
    UT_hash_handle hh;
} entry_t;

static inline void compkey_init(compkey_t *k, int tenant, int user) {
    memset(k, 0, sizeof(*k)); // avoid padding differences
    k->tenant_id = tenant;
    k->user_id   = user;
}

int main(void) {
    entry_t *map = NULL, *e, *tmp;

    // INSERT
    int tenants[] = {1,1,2};
    int users[]   = {10,11,10};
    const char *roles[] = {"admin","viewer","editor"};

    for (int i = 0; i < 3; i++) {
        e = malloc(sizeof(*e));
        compkey_init(&e->key, tenants[i], users[i]);
        e->role = strdup(roles[i]);
        HASH_ADD(hh, map, key, sizeof(e->key), e);
    }

    // FIND
    compkey_t k;
    compkey_init(&k, 1, 11);
    HASH_FIND(hh, map, &k, sizeof(k), e);
    if (e) printf("Found t=%d u=%d role=%s\n", e->key.tenant_id, e->key.user_id, e->role);

    // DELETE ONE
    if (e) {
        HASH_DEL(map, e);
        free(e->role);
        free(e);
    }

    // CLEANUP
    HASH_ITER(hh, map, e, tmp) {
        HASH_DEL(map, e);
        free(e->role);
        free(e);
    }
    return 0;
}

Compilation

Make sure uthash.h is put in the same directory as your application or in a different directory within your include path.No additional libraries are needed. If your uthash.h is put in third_party/uthash, you can compile with:

gcc -O2 -Wall -Wextra Ithird_party/uthash -o myapp myapp.c

uthash Macro Reference Table

Key TypeRequired Struct SetupInsert MacroLookup MacroDelete MacroNotes
Integer keyint id; … UT_hash_handle hh; (must be last field)HASH_ADD_INT(head, id, item);HASH_FIND_INT(head, &id, out);HASH_DEL(head, item);Fastest and simplest form; great for numeric IDs.
String keychar *key; … UT_hash_handle hh; (last field)HASH_ADD_STR(head, key, item); or HASH_ADD_KEYPTR(hh, head, item->key, strlen(item->key), item);HASH_FIND_STR(head, "keystr", out);HASH_DEL(head, item);You must manage string memory yourself (e.g., strdup + free).
Struct keycompkey_t key; … UT_hash_handle hh; (last field)HASH_ADD(hh, head, key, sizeof(item->key), item);HASH_FIND(hh, head, &key, sizeof(key), out);HASH_DEL(head, item);Zero padding in the key struct before use to avoid mismatches.

Best Practice When Using Uthash

  • Always put UT_hash_handle hh last in your struct — uthash depends on this field for internal bookkeeping.
  • Manage your own memory: uthash never allocates or frees your payload data. Use malloc/free (and strdup/free for strings) carefully.
  • Insert entries safely:
    • HASH_ADD_INT for integer keys
    • HASH_ADD_STR or HASH_ADD_KEYPTR for strings
    • HASH_ADD with sizeof(key) for struct/composite keys
  • Find entries with the matching macro (HASH_FIND_INT, HASH_FIND_STR, HASH_FIND). Always check the result pointer for NULL.
  • Delete with HASH_DEL and then free your allocated memory — uthash won’t free the struct automatically.
  • Iterate safely with HASH_ITER when you need to walk or delete all entries.
  • Zero out composite keys before setting values to avoid false mismatches due to padding bytes.
  • Keep it single-threaded: uthash itself is not thread-safe — wrap access in locks if you need concurrency.
  • Use -Wall -Wextra when compiling to catch common mistakes early.

Related

Tags:

Join the conversation

Your email address will not be published. Required fields are marked *