diff --git a/src/datastructures.c b/src/datastructures.c
index a12707ef9758db93ad79dc052533b40cdd4edcbb..bd9b4ebefb4e8069a983e153decb0e3059737af6 100644
--- a/src/datastructures.c
+++ b/src/datastructures.c
@@ -232,6 +232,7 @@ int   h_hashtable_present(const HHashTable* ht, const void* key) {
   }
   return false;
 }
+
 void  h_hashtable_del(HHashTable* ht, const void* key) {
   HHashValue hashval = ht->hashFunc(key);
 #ifdef CONSISTENCY_CHECK
@@ -257,6 +258,7 @@ void  h_hashtable_del(HHashTable* ht, const void* key) {
     }
   }
 }
+
 void  h_hashtable_free(HHashTable* ht) {
   for (size_t i = 0; i < ht->capacity; i++) {
     HHashTableEntry *hten, *hte = &ht->contents[i];
@@ -272,6 +274,56 @@ void  h_hashtable_free(HHashTable* ht) {
   h_arena_free(ht->arena, ht->contents);
 }
 
+// helper for hte_equal
+static bool hte_same_length(HHashTableEntry *xs, HHashTableEntry *ys) {
+  for(; xs && ys; xs=xs->next, ys=ys->next) {
+    // skip NULL keys (= element not present)
+    if(xs->key == NULL) xs=xs->next;
+    if(ys->key == NULL) ys=ys->next;
+  }
+  return (xs == ys);    // both NULL
+}
+
+// helper for hte_equal: are all elements of xs present in ys?
+static bool hte_subset(HEqualFunc eq, HHashTableEntry *xs, HHashTableEntry *ys)
+{
+  for(; xs; xs=xs->next) {
+    if(xs->key == NULL) continue;   // element not present
+
+    HHashTableEntry *hte;
+    for(hte=ys; hte; hte=hte->next) {
+      if(hte->key == xs->key) break; // assume an element is equal to itself
+      if(hte->hashval != xs->hashval) continue; // shortcut
+      if(eq(hte->key, xs->key)) break;
+    }
+    if(hte == NULL) return false;   // element not found
+  }
+  return true;                      // all found
+}
+
+// compare two lists of HHashTableEntries
+static inline bool hte_equal(HEqualFunc eq, HHashTableEntry *xs, HHashTableEntry *ys) {
+  return (hte_same_length(xs, ys) && hte_subset(eq, xs, ys));
+}
+
+/* Set equality of HHashSets.
+ * Obviously, 'a' and 'b' must use the same equality function.
+ * Not strictly necessary, but we also assume the same hash function.
+ */
+bool h_hashset_equal(const HHashSet *a, const HHashSet *b) {
+  if(a->capacity == b->capacity) {
+    // iterate over the buckets in parallel
+    for(size_t i=0; i < a->capacity; i++) {
+      if(!hte_equal(a->equalFunc, &a->contents[i], &b->contents[i]))
+        return false;
+    }
+  } else {
+    assert_message(0, "h_hashset_equal called on sets of different capacity");
+    // TODO implement general case
+  }
+  return true;
+}
+
 bool h_eq_ptr(const void *p, const void *q) {
   return (p==q);
 }
diff --git a/src/internal.h b/src/internal.h
index 01861f5e70189dedb3313a0db28f1ddf02f3c9a1..11836826c9771ed8d46302ec8c4f118ce14fac0f 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -272,6 +272,7 @@ typedef HHashTable HHashSet;
 #define h_hashset_empty(ht)      h_hashtable_empty(ht)
 #define h_hashset_del(ht,el)     h_hashtable_del(ht,el)
 #define h_hashset_free(ht)       h_hashtable_free(ht)
+bool h_hashset_equal(const HHashSet *a, const HHashSet *b);
 
 bool h_eq_ptr(const void *p, const void *q);
 HHashValue h_hash_ptr(const void *p);