Repository: unum-cloud/usearch (language: python)

Issue:
Bug: JS keys should be `bigint`
### Describe the bug

`Index::Add` currently validates the key expecting it to be a `number`.

### Steps to reproduce

N/A

### Expected behavior

The validation should expect it to be a `bigint`.

### USearch version

#20566e0ecdd99564150602dd96610e4be377e080

### Operating System

N/A

### Hardware architecture

Arm

### Which interface are you using?

Other bindings

### Contact Details

_No response_

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion / hints:

Fix patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 7add48cd..091ada10 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -157,5 +157,10 @@
         "reportMissingImports": "none"
     },
     "python.testing.unittestEnabled": false,
-    "python.testing.pytestEnabled": true
+    "python.testing.pytestEnabled": true,
+    "[go]": {
+        "editor.insertSpaces": true,
+        "editor.formatOnSave": true,
+        "editor.defaultFormatter": "golang.go"
+    },
 }
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c4c17e2e..f6c19cb2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,6 +14,7 @@ option(USEARCH_INSTALL "Install CMake targets" OFF)
 option(USEARCH_BUILD_TEST "Compile a native unit test in C++" ${USEARCH_IS_MAIN_PROJECT})
 option(USEARCH_BUILD_BENCHMARK "Compile a native benchmark in C++" ${USEARCH_IS_MAIN_PROJECT})
 option(USEARCH_BUILD_WOLFRAM "Compile Wolfram Language bindings" OFF)
+option(USEARCH_BUILD_C "Compile a native library for the C 99 interface" OFF)
 
 # Includes
 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
@@ -122,3 +123,7 @@ endif()
 if(${USEARCH_BUILD_WASM})
     add_subdirectory(wasm)
 endif()
+
+if(${USEARCH_BUILD_C})
+    add_subdirectory(c)
+endif()
diff --git a/README.md b/README.md
index 51e17e4d..21adf34f 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ Linux • MacOS • Windows • Docker • WebAssembly
 - ✅ SIMD-optimized and [user-defined metrics](#user-defined-functions) with JIT compilation.
 - ✅ Variable dimensionality vectors for unique applications, including search over compressed data.
 - ✅ Bitwise Tanimoto and Sorensen coefficients for [Genomics and Chemistry applications](#usearch--rdkit--molecular-search).
-- ✅ Hardware-agnostic `f16` & `f8` - [half-precision & quarter-precision support](#memory-efficiency-downcasting-and-quantization).
+- ✅ Hardware-agnostic `f16` & `i8` - [half-precision & quarter-precision support](#memory-efficiency-downcasting-and-quantization).
 - ✅ [View large indexes from disk](#disk-based-indexes) without loading into RAM.
 - ✅ Space-efficient point-clouds with `uint40_t`, accommodating 4B+ size.
 - ✅ Compatible with OpenMP and custom "executors", for fine-grained control over CPU utilization.
@@ -79,7 +79,7 @@ from usearch.index import Index
 index = Index(
     ndim=3, # Define the number of dimensions in input vectors
     metric='cos', # Choose 'l2sq', 'haversine' or other metric, default = 'ip'
-    dtype='f32', # Quantize to 'f16' or 'f8' if needed, default = 'f32'
+    dtype='f32', # Quantize to 'f16' or 'i8' if needed, default = 'f32'
     connectivity=16, # Optional: How frequent should the connections in the graph be
     expansion_add=128, # Optional: Control the recall of indexing
     expansion_search=64, # Optional: Control the quality of search
@@ -121,10 +121,10 @@ Those, however, are only sometimes reliable, can significantly affect the statis
 ![USearch uint40_t support](https://github.com/unum-cloud/usearch/blob/main/assets/usearch-neighbor-types.png?raw=true)
 
 Instead, we have focused on high-precision arithmetic over low-precision downcasted vectors.
-The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f32_t`, `f16_t`, `f64_t`, and `f8_t` representations, even if the hardware doesn't natively support it.
+The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f32_t`, `f16_t`, `f64_t`, and `i8_t` representations, even if the hardware doesn't natively support it.
 Continuing the topic of memory efficiency, we provide a `uint40_t` to allow collection with over 4B+ vectors without allocating 8 bytes for every neighbor reference in the proximity graph.
 
-|              | FAISS, `f32` | USearch, `f32` | USearch, `f16` |     USearch, `f8` |
+|              | FAISS, `f32` | USearch, `f32` | USearch, `f16` |     USearch, `i8` |
 | :----------- | -----------: | -------------: | -------------: | ----------------: |
 | Batch Insert |       16 K/s |         73 K/s |        100 K/s | 104 K/s **+550%** |
 | Batch Search |       82 K/s |        103 K/s |        113 K/s |  134 K/s **+63%** |
diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
new file mode 100644
index 00000000..fc7aed52
--- /dev/null
+++ b/c/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(USEARCH_PUNNED_INCLUDE_DIRS
+    "${CMAKE_CURRENT_SOURCE_DIR}/../include"
+    "${CMAKE_CURRENT_SOURCE_DIR}/../fp16/include"
+    "${CMAKE_CURRENT_SOURCE_DIR}/../robin-map/include"
+)
+
+add_executable(usearch lib.cpp)
+target_include_directories(usearch PRIVATE ${USEARCH_PUNNED_INCLUDE_DIRS})
+set_target_properties(usearch PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/c)
+set_target_properties(usearch PROPERTIES CXX_STANDARD 11)
diff --git a/c/Makefile b/c/Makefile
index b13bfe5d..5973f0e7 100644
--- a/c/Makefile
+++ b/c/Makefile
@@ -1,10 +1,10 @@
-CC = gcc-12
-CXX = g++-12
+CC = gcc
+CXX = g++
 
 C_FLAGS = -std=c99
-CXX_FLAGS = -std=c++17
+CXX_FLAGS = -std=c++11
 
-CXX_FLAGS += -Wall -Wextra -Wno-conversion -Wno-unknown-pragmas -O3 -march=native
+CXX_FLAGS += -Wall -Wextra -Wno-conversion -Wno-unknown-pragmas -O3 -march=native -pedantic
 
 HEADER_INCLUDES = -I.  -I ../include/  -I ../fp16/include/ -I ../robin-map/include/
 
diff --git a/c/lib.cpp b/c/lib.cpp
index 7ea26c91..76b9c703 100644
--- a/c/lib.cpp
+++ b/c/lib.cpp
@@ -41,7 +41,7 @@ scalar_kind_t to_native_scalar(usearch_scalar_kind_t kind) {
     case usearch_scalar_f32_k: return scalar_kind_t::f32_k;
     case usearch_scalar_f64_k: return scalar_kind_t::f64_k;
     case usearch_scalar_f16_k: return scalar_kind_t::f16_k;
-    case usearch_scalar_f8_k: return scalar_kind_t::f8_k;
+    case usearch_scalar_i8_k: return scalar_kind_t::i8_k;
     case usearch_scalar_b1_k: return scalar_kind_t::b1x8_k;
     default: return scalar_kind_t::unknown_k;
     }
@@ -52,7 +52,7 @@ add_result_t add_(index_dense_t* index, usearch_key_t key, void const* vector, s
     case scalar_kind_t::f32_k: return index->add(key, (f32_t const*)vector);
     case scalar_kind_t::f64_k: return index->add(key, (f64_t const*)vector);
     case scalar_kind_t::f16_k: return index->add(key, (f16_t const*)vector);
-    case scalar_kind_t::f8_k: return index->add(key, (f8_bits_t const*)vector);
+    case scalar_kind_t::i8_k: return index->add(key, (i8_bits_t const*)vector);
     case scalar_kind_t::b1x8_k: return index->add(key, (b1x8_t const*)vector);
     default: return add_result_t{}.failed("Unknown scalar kind!");
     }
@@ -63,7 +63,7 @@ bool get_(index_dense_t* index, usearch_key_t key, void* vector, scalar_kind_t k
     case scalar_kind_t::f32_k: return index->get(key, (f32_t*)vector);
     case scalar_kind_t::f64_k: return index->get(key, (f64_t*)vector);
     case scalar_kind_t::f16_k: return index->get(key, (f16_t*)vector);
-    case scalar_kind_t::f8_k: return index->get(key, (f8_bits_t*)vector);
+    case scalar_kind_t::i8_k: return index->get(key, (i8_bits_t*)vector);
     case scalar_kind_t::b1x8_k: return index->get(key, (b1x8_t*)vector);
     default: return search_result_t().failed("Unknown scalar kind!");
     }
@@ -74,7 +74,7 @@ search_result_t search_(index_dense_t* index, void const* vector, scalar_kind_t
     case scalar_kind_t::f32_k: return index->search((f32_t const*)vector, n);
     case scalar_kind_t::f64_k: return index->search((f64_t const*)vector, n);
     case scalar_kind_t::f16_k: return index->search((f16_t const*)vector, n);
-    case scalar_kind_t::f8_k: return index->search((f8_bits_t const*)vector, n);
+    case scalar_kind_t::i8_k: return index->search((i8_bits_t const*)vector, n);
     case scalar_kind_t::b1x8_k: return index->search((b1x8_t const*)vector, n);
     default: return search_result_t().failed("Unknown scalar kind!");
     }
diff --git a/c/usearch.h b/c/usearch.h
index d3469a51..6f9cf7a6 100644
--- a/c/usearch.h
+++ b/c/usearch.h
@@ -13,30 +13,43 @@ extern "C" {
 USEARCH_EXPORT typedef void* usearch_index_t;
 USEARCH_EXPORT typedef uint64_t usearch_key_t;
 USEARCH_EXPORT typedef float usearch_distance_t;
+
+/**
+ *  @brief  Pointer to a null-terminated error message.
+ *          Returned error messages @b don't need to be deallocated.
+ */
 USEARCH_EXPORT typedef char const* usearch_error_t;
 
+/**
+ *  @brief  Type-punned callback for "metrics" or "distance functions",
+ *          that accepts pointers to two vectors and measures their @b dis-similarity.
+ */
 USEARCH_EXPORT typedef usearch_distance_t (*usearch_metric_t)(void const*, void const*);
 
+/**
+ *  @brief  Enumerator for the most common kinds of `usearch_metric_t`.
+ *          Those are supported out of the box, with SIMD-optimizations for most common hardware.
+ */
 USEARCH_EXPORT typedef enum usearch_metric_kind_t {
-    usearch_metric_ip_k = 0,
-    usearch_metric_l2sq_k,
+    usearch_metric_unknown_k = 0,
     usearch_metric_cos_k,
+    usearch_metric_ip_k,
+    usearch_metric_l2sq_k,
     usearch_metric_haversine_k,
     usearch_metric_pearson_k,
     usearch_metric_jaccard_k,
     usearch_metric_hamming_k,
     usearch_metric_tanimoto_k,
     usearch_metric_sorensen_k,
-    usearch_metric_unknown_k,
 } usearch_metric_kind_t;
 
 USEARCH_EXPORT typedef enum usearch_scalar_kind_t {
-    usearch_scalar_f32_k = 0,
+    usearch_scalar_unknown_k = 0,
+    usearch_scalar_f32_k,
     usearch_scalar_f64_k,
     usearch_scalar_f16_k,
-    usearch_scalar_f8_k,
+    usearch_scalar_i8_k,
     usearch_scalar_b1_k,
-    usearch_scalar_unknown_k,
 } usearch_scalar_kind_t;
 
 USEARCH_EXPORT typedef struct usearch_init_options_t {
@@ -88,21 +101,21 @@ USEARCH_EXPORT void usearch_free(usearch_index_t, usearch_error_t* error);
 
 /**
  *  @brief Saves the index to a file.
- *  @param path The file path where the index will be saved.
+ *  @param[in] path The file path where the index will be saved.
  *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  */
 USEARCH_EXPORT void usearch_save(usearch_index_t, char const* path, usearch_error_t* error);
 
 /**
  *  @brief Loads the index from a file.
- *  @param path The file path from where the index will be loaded.
+ *  @param[in] path The file path from where the index will be loaded.
  *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  */
 USEARCH_EXPORT void usearch_load(usearch_index_t, char const* path, usearch_error_t* error);
 
 /**
  *  @brief Creates a view of the index from a file without loading it into memory.
- *  @param path The file path from where the view will be created.
+ *  @param[in] path The file path from where the view will be created.
  *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  */
 USEARCH_EXPORT void usearch_view(usearch_index_t, char const* path, usearch_error_t* error);
@@ -114,16 +127,16 @@ USEARCH_EXPORT size_t usearch_connectivity(usearch_index_t, usearch_error_t* err
 
 /**
  *  @brief Reserves memory for a specified number of incoming vectors.
- *  @param capacity The desired total capacity including current size.
+ *  @param[in] capacity The desired total capacity including current size.
  *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  */
 USEARCH_EXPORT void usearch_reserve(usearch_index_t, size_t capacity, usearch_error_t* error);
 
 /**
  *  @brief Adds a vector with a key to the index.
- *  @param key The key associated with the vector.
- *  @param vector Pointer to the vector data.
- *  @param vector_kind The scalar type used in the vector data.
+ *  @param[in] key The key associated with the vector.
+ *  @param[in] vector Pointer to the vector data.
+ *  @param[in] vector_kind The scalar type used in the vector data.
  *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  */
 USEARCH_EXPORT void usearch_add(        //
@@ -132,25 +145,31 @@ USEARCH_EXPORT void usearch_add(        //
 
 /**
  *  @brief Checks if the index contains a vector with a specific key.
- *  @param key The key to be checked.
+ *  @param[in] key The key to be checked.
  *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  *  @return `true` if the index contains the vector with the given key, `false` otherwise.
  */
 USEARCH_EXPORT bool usearch_contains(usearch_index_t, usearch_key_t, usearch_error_t* error);
 
 /**
- *  @brief Performs k-Approximate Nearest Neighbors Search.
+ *  @brief Performs k-Approximate Nearest Neighbors (kANN) Search for closest vectors to query.
+ *  @param[in] query_vector Pointer to the query vector data.
+ *  @param[in] query_kind The scalar type used in the query vector data.
+ *  @param[in] results_limit Upper bound on the number of neighbors to search, the "k" in "kANN".
+ *  @param[out] found_keys Output buffer for up to `results_limit` nearest neighbors keys.
+ *  @param[out] found_distances Output buffer for up to `results_limit` distances to nearest neighbors.
+ *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  *  @return Number of found matches.
  */
 USEARCH_EXPORT size_t usearch_search(                                                                  //
     usearch_index_t, void const* query_vector, usearch_scalar_kind_t query_kind, size_t results_limit, //
-    usearch_key_t* found_labels, usearch_distance_t* found_distances, usearch_error_t* error);
+    usearch_key_t* found_keys, usearch_distance_t* found_distances, usearch_error_t* error);
 
 /**
  *  @brief Retrieves the vector associated with the given key from the index.
- *  @param key The key of the vector to retrieve.
+ *  @param[in] key The key of the vector to retrieve.
  *  @param[out] vector Pointer to the memory where the vector data will be copied.
- *  @param vector_kind The scalar type used in the vector data.
+ *  @param[in] vector_kind The scalar type used in the vector data.
  *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  *  @return `true` if the vector is successfully retrieved, `false` if the vector is not found.
  */
@@ -160,7 +179,7 @@ USEARCH_EXPORT bool usearch_get(        //
 
 /**
  *  @brief Removes the vector associated with the given key from the index.
- *  @param key The key of the vector to be removed.
+ *  @param[in] key The key of the vector to be removed.
  *  @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
  *  @return `true` if the vector is successfully removed, `false` if the vector is not found.
  */
diff --git a/cpp/README.md b/cpp/README.md
index 68901a69..8ac1542b 100644
--- a/cpp/README.md
+++ b/cpp/README.md
@@ -81,7 +81,7 @@ Instead of spawning additional threads within USearch, we focus on the thread sa
 ```cpp
 #pragma omp parallel for
     for (std::size_t i = 0; i < n; ++i)
-        native.add(key, span_t{vector, dims}, index_add_config_t { .thread = omp_get_thread_num() });
+        native.add(key, span_t{vector, dims}, index_update_config_t { .thread = omp_get_thread_num() });
 ```
 
 During initialization, we allocate enough temporary memory for all the cores on the machine.
diff --git a/cpp/bench.cpp b/cpp/bench.cpp
index 82968f68..be9c21dc 100644
--- a/cpp/bench.cpp
+++ b/cpp/bench.cpp
@@ -286,7 +286,7 @@ void index_many(index_at& index, std::size_t n, vector_id_at const* ids, real_at
 #pragma omp parallel for schedule(static, 32)
 #endif
     for (std::size_t i = 0; i < n; ++i) {
-        index_add_config_t config;
+        index_update_config_t config;
 #if USEARCH_USE_OPENMP
         config.thread = omp_get_thread_num();
 #endif
@@ -373,7 +373,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
                     if (progress % 1000 == 0)
                         printer.print(progress, total);
                 });
-            join_attempts = result.cycles;
+            join_attempts = result.visited_members;
         }
     }
     // Evaluate join quality
@@ -451,7 +451,7 @@ struct args_t {
     bool big = false;
 
     bool quantize_f16 = false;
-    bool quantize_f8 = false;
+    bool quantize_i8 = false;
     bool quantize_b1 = false;
 
     bool metric_ip = false;
@@ -481,8 +481,8 @@ struct args_t {
     scalar_kind_t quantization() const noexcept {
         if (quantize_f16)
             return scalar_kind_t::f16_k;
-        if (quantize_f8)
-            return scalar_kind_t::f8_k;
+        if (quantize_i8)
+            return scalar_kind_t::i8_k;
         if (quantize_b1)
             return scalar_kind_t::b1x8_k;
         return scalar_kind_t::f32_k;
@@ -553,7 +553,7 @@ int main(int argc, char** argv) {
         (option("--rows-take") & value("integer", args.vectors_to_take)).doc("Number of vectors to take"),
         ( //
             option("-f16", "--f16quant").set(args.quantize_f16).doc("Enable `f16_t` quantization") |
-            option("-f8", "--f8quant").set(args.quantize_f8).doc("Enable `f8_t` quantization") |
+            option("-i8", "--i8quant").set(args.quantize_i8).doc("Enable `i8_t` quantization") |
             option("-b1", "--b1quant").set(args.quantize_b1).doc("Enable `b1x8_t` quantization")),
         ( //
             option("--ip").set(args.metric_ip).doc("Choose Inner Product metric") |
diff --git a/docs/benchmarks.md b/docs/benchmarks.md
index 6ee63816..63d5880d 100644
--- a/docs/benchmarks.md
+++ b/docs/benchmarks.md
@@ -46,7 +46,7 @@ The main columns are:
 | `f32` x256   |      16      |  128   |   64   |    87'995    |     171'856     |         99.1% |
 | `f16` x256   |      16      |  128   |   64   |    87'270    |     153'788     |         98.4% |
 | `f16` x256 ✳️ |      16      |  128   |   64   |    71'454    |     132'673     |         98.4% |
-| `f8` x256    |      16      |  128   |   64   |   115'923    |     274'653     |         98.9% |
+| `i8` x256    |      16      |  128   |   64   |   115'923    |     274'653     |         98.9% |
 
 As seen on the chart, for `f16` quantization, performance may differ depending on native hardware support for that numeric type.
 Also worth noting, 8-bit quantization results in almost no quantization loss and may perform better than `f16`.
diff --git a/docs/compilation.md b/docs/compilation.md
index 5c72f2f3..6d00aff0 100644
--- a/docs/compilation.md
+++ b/docs/compilation.md
@@ -143,34 +143,51 @@ java -cp . -Djava.library.path="$(pwd)/java/cloud/unum/usearch/" Index.java
 java -cp . -Djava.library.path="$(pwd)/java" cloud.unum.usearch.Index
 ```
 
-## Wolfram
+## Objective-C and Swift
 
 ```sh
-brew install --cask wolfram-engine
+swift build
+swift test -v
 ```
 
-## GoLang
+## C 99
+
+There are a few ways to compile the C 99 USearch SDK.
+Using the Makefile:
 
 ```sh
-cd golang
-go test -v
+make -C ./c build
 ```
 
-## Objective-C and Swift
+Using CMake:
 
 ```sh
-swift build
-swift test -v
+cmake -B ./build_release -DUSEARCH_BUILD_C=1 && make -C ./build_release -j
 ```
 
-## C99
-
 Linux:
 
 ```sh
 g++ -std=c++11 -shared -fPIC c/lib.cpp -I ./include/  -I ./fp16/include/ -I ./robin-map/include/ -o libusearch.so
 ```
 
+
+## GoLang
+
+GoLang bindings are based on C.
+So one should first compile the C library, link it with GoLang, and only then run tests.
+
+```sh
+cd golang && make -C ../c build && mv ../c/libusearch.so libusearch.so && go test -v
+```
+
+## Wolfram
+
+```sh
+brew install --cask wolfram-engine
+```
+
+
 ## Docker
 
 ```sh
diff --git a/golang/lib.go b/golang/lib.go
index caefbfcd..08890f91 100644
--- a/golang/lib.go
+++ b/golang/lib.go
@@ -2,49 +2,63 @@ package usearch
 
 import (
 	"errors"
-	"fmt"
 	"unsafe"
 )
 
 /*
-#include "../c/usearch.h"
-
-#cgo CPPFLAGS: -I.
-#cgo LDFLAGS: -lusearch
+#cgo CFLAGS: -I${SRCDIR}/../c/
+#cgo LDFLAGS: -L./ -lusearch
+#include <usearch.h>
+#include <stdlib.h>
 */
 import "C"
 
-type DistMetric int8
+type Key = uint64
+
+type Metric uint8
 
 const (
-	L2_SQ DistMetric = iota
-	IP
-	COS
+	InnerProduct Metric = iota
+	Cosine
+	L2sq
 	Haversine
+	Pearson
+	Hamming
+	Tanimoto
+	Sorensen
 )
 
-func (m DistMetric) String() string {
+func (m Metric) String() string {
 	switch m {
-	case L2_SQ:
+	case L2sq:
 		return "l2sq"
-	case IP:
+	case InnerProduct:
 		return "ip"
-	case COS:
+	case Cosine:
 		return "cos"
 	case Haversine:
 		return "haversine"
+	case Pearson:
+		return "pearson"
+	case Hamming:
+		return "hamming"
+	case Tanimoto:
+		return "tanimoto"
+	case Sorensen:
+		return "sorensen"
 	default:
-		panic("unknown metric")
+		panic("Unknown metric")
 	}
 }
 
-type Quantization int8
+type Quantization uint8
 
 const (
 	f32 Quantization = iota
 	f16
 	f64
-	f8
+	i8
+	b1
 )
 
 func (a Quantization) String() string {
@@ -55,30 +69,38 @@ func (a Quantization) String() string {
 		return "f32"
 	case f64:
 		return "f64"
-	case f8:
-		return "f8"
+	case i8:
+		return "i8"
+	case b1:
+		return "b1"
 	default:
-		panic("unknown quantization")
+		panic("Unknown quantization")
 	}
 }
 
 type IndexConfig struct {
-	Quantization        Quantization
-	Metric          DistMetric
-	VecDimension    int
-	InitCapacity    int
-	Connectivity    int
-	ExpansionAdd    int
-	ExpansionSearch int
+	// The scalar kind used for quantization of vector data during indexing.
+	Quantization Quantization
+	// The metric kind used for distance calculation between vectors.
+	Metric Metric
+	// The number of dimensions in the vectors to be indexed.
+	Dimensions uint
+	// The @b optional connectivity parameter that limits connections-per-node in graph.
+	Connectivity uint
+	// The @b optional expansion factor used for index construction when adding vectors.
+	ExpansionAdd uint
+	// The @b optional expansion factor used for index construction during search operations.
+	ExpansionSearch uint
 }
 
-func DefaultConfig(dimension int) IndexConfig {
+func DefaultConfig(dimensions uint) IndexConfig {
 	c := IndexConfig{}
-	c.InitCapacity = 32
-	c.Connectivity = 16
-	c.VecDimension = dimension
-	c.ExpansionAdd = 128
-	c.ExpansionSearch = 64
+	c.Dimensions = dimensions
+	c.Metric = Cosine
+	// Zeros will be replaced by the underlying C implementation
+	c.Connectivity = 0
+	c.ExpansionAdd = 0
+	c.ExpansionSearch = 0
 	return c
 }
 
@@ -87,152 +109,264 @@ type Index struct {
 	config        IndexConfig
 }
 
-func NewIndex(conf IndexConfig) *Index {
-	ind := &Index{config: conf}
-	ind.init()
-	return ind
-}
+// Initializes a new instance of the index.
+func NewIndex(conf IndexConfig) (index *Index, err error) {
+	index = &Index{config: conf}
 
-func (ind *Index) init() {
-	conf := ind.config
-	metric_str := C.CString(conf.Metric.String())
-	defer C.free(unsafe.Pointer(metric_str))
-	metric_len := C.int(len(conf.Metric.String()))
-	quantization_str := C.CString(conf.Quantization.String())
-	defer C.free(unsafe.Pointer(quantization_str))
-	quantization_len := C.int(len(conf.Quantization.String()))
-	dimensions := C.int(conf.VecDimension)
-	capacity := C.int(conf.InitCapacity)
-	connectivity := C.int(conf.Connectivity)
-	expansion_add := C.int(conf.ExpansionAdd)
-	expansion_search := C.int(conf.ExpansionSearch)
-	ptr := C.usearch_new(metric_str, metric_len,
-		quantization_str, quantization_len,
-		dimensions, capacity, connectivity,
-		expansion_add, expansion_search)
+	conf = index.config
+	dimensions := C.ulong(conf.Dimensions)
+	connectivity := C.ulong(conf.Connectivity)
+	expansion_add := C.ulong(conf.ExpansionAdd)
+	expansion_search := C.ulong(conf.ExpansionSearch)
 
-	ind.opaque_handle = (*C.void)(unsafe.Pointer(ptr))
-}
+	options := C.struct_usearch_init_options_t{}
+	options.dimensions = dimensions
+	options.connectivity = connectivity
+	options.expansion_add = expansion_add
+	options.expansion_search = expansion_search
 
-func (ind *Index) Destroy() {
-	if ind.opaque_handle == nil {
-		panic("index not initialized")
+	// Map the metric kind to a C enum
+	switch conf.Metric {
+	case L2sq:
+		options.metric_kind = C.usearch_metric_l2sq_k
+	case InnerProduct:
+		options.metric_kind = C.usearch_metric_ip_k
+	case Cosine:
+		options.metric_kind = C.usearch_metric_cos_k
+	case Haversine:
+		options.metric_kind = C.usearch_metric_haversine_k
+	case Pearson:
+		options.metric_kind = C.usearch_metric_pearson_k
+	case Hamming:
+		options.metric_kind = C.usearch_metric_hamming_k
+	case Tanimoto:
+		options.metric_kind = C.usearch_metric_tanimoto_k
+	case Sorensen:
+		options.metric_kind = C.usearch_metric_sorensen_k
+	default:
+		options.metric_kind = C.usearch_metric_unknown_k
 	}
-	C.usearch_free(unsafe.Pointer(ind.opaque_handle))
-	ind.opaque_handle = nil
-	ind.config = IndexConfig{}
-}
 
-func (ind *Index) fileOp(path string, op string) error {
-	if ind.opaque_handle == nil {
-		panic("index not initialized")
-	}
-	c_path := C.CString(path)
-	defer C.free(unsafe.Pointer(c_path))
-	var errStr *C.char
-	switch op {
-	case "load":
-		errStr = C.usearch_load(unsafe.Pointer(ind.opaque_handle), c_path)
-	case "save":
-		errStr = C.usearch_save(unsafe.Pointer(ind.opaque_handle), c_path)
-	case "view":
-		errStr = C.usearch_view(unsafe.Pointer(ind.opaque_handle), c_path)
+	// Map the quantization method
+	switch conf.Quantization {
+	case f16:
+		options.quantization = C.usearch_scalar_f16_k
+	case f32:
+		options.quantization = C.usearch_scalar_f32_k
+	case f64:
+		options.quantization = C.usearch_scalar_f64_k
+	case i8:
+		options.quantization = C.usearch_scalar_i8_k
+	case b1:
+		options.quantization = C.usearch_scalar_b1_k
 	default:
-		panic("unknown file operation")
+		options.quantization = C.usearch_scalar_unknown_k
+	}
+
+	var errorMessage *C.char
+	ptr := C.usearch_init(&options, (*C.usearch_error_t)(&errorMessage))
+	if errorMessage != nil {
+		return nil, errors.New(C.GoString(errorMessage))
 	}
-	var err error
-	if errStr != nil {
-		err = errors.New(C.GoString(errStr))
-		C.free(unsafe.Pointer(errStr))
+
+	index.opaque_handle = (*C.void)(unsafe.Pointer(ptr))
+	return index, nil
+}
+
+func (index *Index) Len() (len uint, err error) {
+	var errorMessage *C.char
+	len = uint(C.usearch_size((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage)))
+	if errorMessage != nil {
+		err = errors.New(C.GoString(errorMessage))
+	}
+	return len, err
+}
+
+func (index *Index) Connectivity() (con uint, err error) {
+	var errorMessage *C.char
+	con = uint(C.usearch_connectivity((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage)))
+	if errorMessage != nil {
+		err = errors.New(C.GoString(errorMessage))
 	}
-	return err
+	return con, err
 }
 
-func (ind *Index) Save(path string) error {
-	return ind.fileOp(path, "save")
+func (index *Index) Dimensions() (dim uint, err error) {
+	var errorMessage *C.char
+	dim = uint(C.usearch_dimensions((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage)))
+	if errorMessage != nil {
+		err = errors.New(C.GoString(errorMessage))
+	}
+	return dim, err
 }
 
-func (ind *Index) Load(path string) error {
-	return ind.fileOp(path, "load")
+func (index *Index) Capacity() (cap uint, err error) {
+	var errorMessage *C.char
+	cap = uint(C.usearch_capacity((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage)))
+	if errorMessage != nil {
+		err = errors.New(C.GoString(errorMessage))
+	}
+	return cap, err
 }
 
-func (ind *Index) View(path string) error {
-	return ind.fileOp(path, "view")
+// Frees the resources associated with the index.
+func (index *Index) Destroy() error {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
+	}
+
+	var errorMessage *C.char
+	C.usearch_free((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (*C.usearch_error_t)(&errorMessage))
+	if errorMessage != nil {
+		return errors.New(C.GoString(errorMessage))
+	}
+	index.opaque_handle = nil
+	index.config = IndexConfig{}
+	return nil
 }
 
-func (ind *Index) Len() int {
-	return int(C.usearch_size(unsafe.Pointer(ind.opaque_handle)))
+// Reserves memory for a specified number of incoming vectors.
+func (index *Index) Reserve(capacity uint) error {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
+	}
+
+	var errorMessage *C.char
+	C.usearch_reserve((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.size_t)(capacity), (*C.usearch_error_t)(&errorMessage))
+	if errorMessage != nil {
+		return errors.New(C.GoString(errorMessage))
+	}
+	return nil
 }
 
-func (ind *Index) Connectivity() int {
-	return int(C.usearch_connectivity(unsafe.Pointer(ind.opaque_handle)))
+// Adds a vector with a key to the index.
+func (index *Index) Add(key Key, vec []float32) error {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
+	}
+
+	var errorMessage *C.char
+	C.usearch_add((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.usearch_key_t)(key), unsafe.Pointer(&vec[0]), C.usearch_scalar_f32_k, (*C.usearch_error_t)(&errorMessage))
+	if errorMessage != nil {
+		return errors.New(C.GoString(errorMessage))
+	}
+	return nil
 }
 
-func (ind *Index) VecDimension() int {
-	return int(C.usearch_dimensions(unsafe.Pointer(ind.opaque_handle)))
+// Removes the vector associated with the given key from the index.
+func (index *Index) Remove(key Key) error {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
+	}
+
+	var errorMessage *C.char
+	C.usearch_remove((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.usearch_key_t)(key), (*C.usearch_error_t)(&errorMessage))
+	if errorMessage != nil {
+		return errors.New(C.GoString(errorMessage))
+	}
+	return nil
 }
 
-func (ind *Index) Capacity() int {
-	return int(C.usearch_capacity(unsafe.Pointer(ind.opaque_handle)))
+// Checks if the index contains a vector with a specific key.
+func (index *Index) Contains(key Key) (found bool, err error) {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
+	}
+
+	var errorMessage *C.char
+	found = bool(C.usearch_contains((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.usearch_key_t)(key), (*C.usearch_error_t)(&errorMessage)))
+	if errorMessage != nil {
+		return found, errors.New(C.GoString(errorMessage))
+	}
+	return found, nil
 }
 
-func (ind *Index) Reserve(capacity int) error {
-	if ind.opaque_handle == nil {
-		panic("index not initialized")
+func (index *Index) Get(key Key) (vector []float32, err error) {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
 	}
-	errStr := C.usearch_reserve(unsafe.Pointer(ind.opaque_handle), (C.int)(capacity))
-	var err error
-	if errStr != nil {
-		err = errors.New(C.GoString(errStr))
-		C.free(unsafe.Pointer(errStr))
+
+	vector = make([]float32, index.config.Dimensions)
+	var errorMessage *C.char
+	found := bool(C.usearch_get((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), (C.usearch_key_t)(key), unsafe.Pointer(&vector[0]), C.usearch_scalar_f32_k, (*C.usearch_error_t)(&errorMessage)))
+	if errorMessage != nil {
+		return nil, errors.New(C.GoString(errorMessage))
 	}
-	return err
+	if !found {
+		return nil, nil
+	}
+	return vector, nil
 }
 
-func (ind *Index) Add(key int, vec []float32) error {
-	if ind.opaque_handle == nil {
-		panic("index not initialized")
+// Performs k-Approximate Nearest Neighbors Search for closest vectors to query.
+func (index *Index) Search(query []float32, limit uint) (keys []Key, distances []float32, err error) {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
+	}
+	if len(query) != int(index.config.Dimensions) {
+		return nil, nil, errors.New("Number of dimensions doesn't match!")
 	}
-	errStr := C.usearch_add(unsafe.Pointer(ind.opaque_handle), (C.int)(key), (*C.float)(&vec[0]))
-	var err error
-	if errStr != nil {
-		err = errors.New(C.GoString(errStr))
-		C.free(unsafe.Pointer(errStr))
+
+	keys = make([]Key, limit)
+	distances = make([]float32, limit)
+	var errorMessage *C.char
+	count := uint(C.usearch_search((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), unsafe.Pointer(&query[0]), C.usearch_scalar_f32_k, (C.size_t)(limit), (*C.usearch_key_t)(&keys[0]), (*C.usearch_distance_t)(&distances[0]), (*C.usearch_error_t)(&errorMessage)))
+	if errorMessage != nil {
+		return nil, nil, errors.New(C.GoString(errorMessage))
 	}
-	return err
 
+	keys = keys[:count]
+	distances = distances[:count]
+	return keys, distances, nil
 }
 
-// return must be int32 because int is 64bit in golang and 32bit in C
-func (ind *Index) Search(query []float32, limit int) []int32 {
-	if ind.opaque_handle == nil {
-		panic("index not initialized")
+// Saves the index to a file.
+func (index *Index) Save(path string) error {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
 	}
-	if len(query) != ind.config.VecDimension {
-		panic(fmt.Sprintf("query vector dimension mismatch. expected %d, got %d",
-			ind.config.VecDimension, len(query)))
+
+	c_path := C.CString(path)
+	defer C.free(unsafe.Pointer(c_path))
+
+	var errorMessage *C.char
+	C.usearch_save((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), c_path, (*C.usearch_error_t)(&errorMessage))
+	if errorMessage != nil {
+		return errors.New(C.GoString(errorMessage))
 	}
-	if limit <= 0 {
-		panic("limit must be greater than 0")
+	return nil
+}
+
+// Loads the index from a file.
+func (index *Index) Load(path string) error {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
 	}
-	res := C.usearch_search(unsafe.Pointer(ind.opaque_handle),
-		(*C.float)(&query[0]), (C.int)(len(query)), (C.int)(limit))
 
-	// my understanding is that search panics in truly exceptional cases,
-	// none of which is every expected. so, not passing the error to the caller
-	if res.Error != nil {
-		defer C.free(unsafe.Pointer(res.Error))
-		panic(C.GoString(res.Error))
+	c_path := C.CString(path)
+	defer C.free(unsafe.Pointer(c_path))
+
+	var errorMessage *C.char
+	C.usearch_load((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), c_path, (*C.usearch_error_t)(&errorMessage))
+	if errorMessage != nil {
+		return errors.New(C.GoString(errorMessage))
 	}
-	var labs []int32
-	//q:: who free's this memory? will golang do it?
-	labs = unsafe.Slice((*int32)(unsafe.Pointer(res.Labels)), res.Len)
-	return labs
+	return nil
 }
 
-func (ind *Index) Size() int {
-	if ind.opaque_handle == nil {
-		panic("index not initialized")
+// Creates a view of the index from a file without loading it into memory.
+func (index *Index) View(path string) error {
+	if index.opaque_handle == nil {
+		panic("Index is uninitialized")
+	}
+
+	c_path := C.CString(path)
+	defer C.free(unsafe.Pointer(c_path))
+
+	var errorMessage *C.char
+	C.usearch_view((C.usearch_index_t)(unsafe.Pointer(index.opaque_handle)), c_path, (*C.usearch_error_t)(&errorMessage))
+	if errorMessage != nil {
+		return errors.New(C.GoString(errorMessage))
 	}
-	return int(C.usearch_size(unsafe.Pointer(ind.opaque_handle)))
+	return nil
 }
diff --git a/golang/usearch.h b/golang/usearch.h
deleted file mode 100644
index b18343d5..00000000
--- a/golang/usearch.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-#include <stdlib.h>
-
-typedef struct {
-    int* Labels;
-    float* Distances;
-    int Len;
-    char* Error;
-} SearchResults;
diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp
index 82a18af9..1a4fa62c 100644
--- a/include/usearch/index.hpp
+++ b/include/usearch/index.hpp
@@ -186,19 +186,19 @@ typename std::enable_if<!std::is_pod<sfinae_at>::value>::type construct_at(at* o
 
 /**
  *  @brief  A reference to a misaligned memory location with a specific type.
- *          It is needed to avoid Undefiend Behavior when dereferencing addresses
+ *          It is needed to avoid Undefined Behavior when dereferencing addresses
  *          indivisible by `sizeof(at)`.
  */
 template <typename at> class misaligned_ref_gt {
     using element_t = at;
-    using constless_t = typename std::remove_const<element_t>::type;
+    using mutable_t = typename std::remove_const<element_t>::type;
     byte_t* ptr_;
 
   public:
     misaligned_ref_gt(byte_t* ptr) noexcept : ptr_(ptr) {}
-    operator constless_t() const noexcept { return misaligned_load<constless_t>(ptr_); }
-    misaligned_ref_gt& operator=(constless_t const& v) noexcept {
-        misaligned_store<constless_t>(ptr_, v);
+    operator mutable_t() const noexcept { return misaligned_load<mutable_t>(ptr_); }
+    misaligned_ref_gt& operator=(mutable_t const& v) noexcept {
+        misaligned_store<mutable_t>(ptr_, v);
         return *this;
     }
 
@@ -208,12 +208,12 @@ template <typename at> class misaligned_ref_gt {
 
 /**
  *  @brief  A pointer to a misaligned memory location with a specific type.
- *          It is needed to avoid Undefiend Behavior when dereferencing addresses
+ *          It is needed to avoid Undefined Behavior when dereferencing addresses
  *          indivisible by `sizeof(at)`.
  */
 template <typename at> class misaligned_ptr_gt {
     using element_t = at;
-    using constless_t = typename std::remove_const<element_t>::type;
+    using mutable_t = typename std::remove_const<element_t>::type;
     byte_t* ptr_;
 
   public:
@@ -265,7 +265,7 @@ template <typename scalar_at> class span_gt {
 };
 
 /**
- *  @brief  Similar to `std::vector`, but doesn't support dyanmic resizing.
+ *  @brief  Similar to `std::vector`, but doesn't support dynamic resizing.
  *          On the bright side, this can't throw exceptions.
  */
 template <typename scalar_at, typename allocator_at> class buffer_gt {
@@ -962,7 +962,7 @@ struct index_limits_t {
     inline std::size_t concurrency() const noexcept { return (std::min)(threads_add, threads_search); }
 };
 
-struct index_add_config_t {
+struct index_update_config_t {
     /// @brief Hyper-parameter controlling the quality of indexing.
     /// Defaults to 40 in FAISS and 200 in hnswlib.
     /// > It is called `efConstruction` in the paper.
@@ -1009,18 +1009,65 @@ using return_type_gt =
     typename std::result_of<metric_at(args_at...)>::type;
 #endif
 
+/**
+ *  @brief  An example of what a USearch-compatible ad-hoc filter would look like.
+ *
+ *  A similar function object can be passed to search queries to further filter entries
+ *  on their auxiliary properties, such as some categorical labels stored in an external DBMS.
+ */
 struct dummy_predicate_t {
     template <typename member_at> constexpr bool operator()(member_at&&) const noexcept { return true; }
 };
 
+/**
+ *  @brief  An example of what a USearch-compatible ad-hoc operation on in-flight entries.
+ *
+ *  This kind of callbacks is used when the engine is being updated and you want to patch
+ *  the entries, while their are still under locks - limiting concurrent access and providing
+ *  consistency.
+ */
 struct dummy_callback_t {
     template <typename member_at> void operator()(member_at&&) const noexcept {}
 };
 
+/**
+ *  @brief  An example of what a USearch-compatible progress-bar should look like.
+ *
+ *  This is particularly helpful when handling long-running tasks, like serialization,
+ *  saving, and loading from disk, or index-level joins.
+ */
 struct dummy_progress_t {
     inline void operator()(std::size_t /*progress*/, std::size_t /*total*/) const noexcept {}
 };
 
+/**
+ *  @brief  An example of what a USearch-compatible values prefetching mechanism should look like.
+ *
+ *  USearch is designed to handle very large datasets, that may not fir into RAM. Fetching from
+ *  external memory is very expensive, so we've added a pre-fetching mechanism, that accepts
+ *  multiple objects at once, to cache in RAM ahead of the computation.
+ *  The received iterators support both `get_slot` and `get_key` operations.
+ *  An example usage may look like this:
+ *
+ *      template <typename member_citerator_like_at>
+ *      inline void operator()(member_citerator_like_at, member_citerator_like_at) const noexcept {
+ *          for (; begin != end; ++begin)
+ *              io_uring_prefetch(offset_in_file(get_key(begin)));
+ *      }
+ */
+struct dummy_prefetch_t {
+    template <typename member_citerator_like_at>
+    inline void operator()(member_citerator_like_at, member_citerator_like_at) const noexcept {}
+};
+
+/**
+ *  @brief  An example of what a USearch-compatible executor (thread-pool) should look like.
+ *
+ *  It's expected to have `execute_bulk(callback)` API to schedule one task per thread;
+ *  an identical `execute_bulk(count, callback)` overload that also accepts the number
+ *  of tasks, and somehow schedules them between threads; as well as `size()` to
+ *  determine the number of available threads.
+ */
 struct dummy_executor_t {
     dummy_executor_t() noexcept {}
     std::size_t size() const noexcept { return 1; }
@@ -1037,6 +1084,13 @@ struct dummy_executor_t {
     }
 };
 
+/**
+ *  @brief  An example of what a USearch-compatible label-to-label mapping should look like.
+ *
+ *  This is particularly helpful for "Semantic Joins", where we map entries of one collection
+ *  to entries of another. In assymetric setups, where A -> B is needed, but B -> A is not,
+ *  this can be passed to minimize memory usage.
+ */
 struct dummy_label_to_label_mapping_t {
     struct member_ref_t {
         template <typename key_at> member_ref_t& operator=(key_at&&) noexcept { return *this; }
@@ -1044,6 +1098,20 @@ struct dummy_label_to_label_mapping_t {
     template <typename key_at> member_ref_t operator[](key_at&&) const noexcept { return {}; }
 };
 
+/**
+ *  @brief  Checks if the provided object has a dummy type, emulating an interface,
+ *          but performaing no real computation.
+ */
+template <typename object_at> static constexpr bool is_dummy() {
+    using object_t = typename std::remove_all_extents<object_at>::type;
+    return std::is_same<object_t, dummy_predicate_t>::value || //
+           std::is_same<object_t, dummy_callback_t>::value ||  //
+           std::is_same<object_t, dummy_progress_t>::value ||  //
+           std::is_same<object_t, dummy_prefetch_t>::value ||  //
+           std::is_same<object_t, dummy_executor_t>::value ||  //
+           std::is_same<object_t, dummy_label_to_label_mapping_t>::value;
+}
+
 template <typename, typename at> struct has_reset_gt {
     static_assert(std::integral_constant<at, false>::value, "Second template parameter needs to be of function type.");
 };
@@ -1159,6 +1227,7 @@ class input_file_t {
             std::fclose(exchange(file_, nullptr));
     }
 
+    explicit operator bool() const noexcept { return file_; }
     bool seek_to(std::size_t progress) noexcept { return std::fseek(file_, progress, SEEK_SET) == 0; }
     bool seek_to_end() noexcept { return std::fseek(file_, 0L, SEEK_END) == 0; }
     bool infer_progress(std::size_t& progress) noexcept {
@@ -1171,7 +1240,7 @@ class input_file_t {
 };
 
 /**
- *  @brief  Represents a memory-mapped file.
+ *  @brief  Represents a memory-mapped file or a pre-allocated anonymous memory region.
  *
  *  This class provides a convenient way to memory-map a file and access its contents as a block of
  *  memory. The class handles platform-specific memory-mapping operations on Windows, Linux, and MacOS.
@@ -1208,6 +1277,8 @@ class memory_mapped_file_t {
     {
     }
 
+    memory_mapped_file_t(byte_t* data, std::size_t length) noexcept : ptr_(data), length_(length) {}
+
     memory_mapped_file_t& operator=(memory_mapped_file_t&& other) noexcept {
         std::swap(path_, other.path_);
         std::swap(ptr_, other.ptr_);
@@ -1223,6 +1294,8 @@ class memory_mapped_file_t {
 
     serialization_result_t open_if_not() noexcept {
         serialization_result_t result;
+        if (!path_ || ptr_)
+            return result;
 
 #if defined(USEARCH_DEFINED_WINDOWS)
 
@@ -1265,7 +1338,7 @@ class memory_mapped_file_t {
         }
 
         // Map the entire file
-        byte_t* file = (byte_t*)mmap(NULL, file_stat.st_size, PROT_READ, MAP_PRIVATE, descriptor, 0);
+        byte_t* file = (byte_t*)mmap(NULL, file_stat.st_size, PROT_READ, MAP_SHARED, descriptor, 0);
         if (file == MAP_FAILED) {
             ::close(descriptor);
             return result.failed(std::strerror(errno));
@@ -1278,8 +1351,11 @@ class memory_mapped_file_t {
     }
 
     void close() noexcept {
-        if (!ptr_)
+        if (!path_) {
+            ptr_ = nullptr;
+            length_ = 0;
             return;
+        }
 #if defined(USEARCH_DEFINED_WINDOWS)
         UnmapViewOfFile(ptr_);
         CloseHandle(mapping_handle_);
@@ -1338,7 +1414,7 @@ template <typename key_at> inline key_at get_key(member_ref_gt<key_at> const& m)
  *  @brief  Approximate Nearest Neighbors Search @b index-structure using the
  *          Hierarchical Navigable Small World @b (HNSW) graphs algorithm.
  *          If classical containers store @b Key->Value mappings, this one can
- *          be seen as a network of keys, accelerating approximate @b Value~>Key lookups.
+ *          be seen as a network of keys, accelerating approximate @b Value~>Key visited_members.
  *
  *  Unlike most implementations, this one is generic anc can be used for any search,
  *  not just within equi-dimensional vectors. Examples range from texts to similar Chess
@@ -1350,8 +1426,8 @@ template <typename key_at> inline key_at get_key(member_ref_gt<key_at> const& m)
  *
  *  @tparam compressed_slot_at
  *      The smallest unsigned integer type to address indexed elements.
- *      It is used intenrally to maximize space-efficiency and is generally
- *      upcasted to @b `std::size_t` in public interfaces.
+ *      It is used internally to maximize space-efficiency and is generally
+ *      up-casted to @b `std::size_t` in public interfaces.
  *      Can be a built-in @b `uint32_t`, `uint64_t`, or our custom @b `uint40_t`.
  *      Which makes the most sense for 4B+ entry indexes.
  *
@@ -1408,7 +1484,7 @@ template <typename key_at> inline key_at get_key(member_ref_gt<key_at> const& m)
  *      -   `member_citerator_t` and `member_iterator_t` have only slots, no indirections.
  *
  *      -   `member_cref_t` and `member_ref_t` contains the `slot` and a reference
- *          to the key. So it passes through 1 level of lookups in `nodes_`.
+ *          to the key. So it passes through 1 level of visited_members in `nodes_`.
  *          Retrieving the key via `get_key` will cause fetching yet another cache line.
  *
  *      -   `member_gt` contains an already prefetched copy of the key.
@@ -1609,7 +1685,7 @@ class index_gt {
         visits_bitset_t visits{};
         std::default_random_engine level_generator{};
         std::size_t iteration_cycles{};
-        std::size_t measurements_count{};
+        std::size_t computed_distances_count{};
 
         template <typename value_at, typename metric_at, typename entry_at> //
         inline distance_t measure(value_at const& first, entry_at const& second, metric_at&& metric) noexcept {
@@ -1617,7 +1693,7 @@ class index_gt {
                 std::is_same<entry_at, member_cref_t>::value || std::is_same<entry_at, member_citerator_t>::value,
                 "Unexpected type");
 
-            measurements_count++;
+            computed_distances_count++;
             return metric(first, second);
         }
 
@@ -1627,7 +1703,7 @@ class index_gt {
                 std::is_same<entry_at, member_cref_t>::value || std::is_same<entry_at, member_citerator_t>::value,
                 "Unexpected type");
 
-            measurements_count++;
+            computed_distances_count++;
             return metric(first, second);
         }
     };
@@ -1854,8 +1930,8 @@ class index_gt {
     struct add_result_t {
         error_t error{};
         std::size_t new_size{};
-        std::size_t lookups{};
-        std::size_t measurements{};
+        std::size_t visited_members{};
+        std::size_t computed_distances{};
         std::size_t slot{};
 
         explicit operator bool() const noexcept { return !error; }
@@ -1884,9 +1960,9 @@ class index_gt {
         /** @brief  Number of search results found. */
         std::size_t count{};
         /** @brief  Number of graph nodes traversed. */
-        std::size_t lookups{};
+        std::size_t visited_members{};
         /** @brief  Number of times the distances were computed. */
-        std::size_t measurements{};
+        std::size_t computed_distances{};
         error_t error{};
 
         inline search_result_t() noexcept {}
@@ -1972,10 +2048,17 @@ class index_gt {
      *  @param[in] config Configuration options for this specific operation.
      *  @param[in] callback On-success callback, executed while the `member_ref_t` is still under lock.
      */
-    template <typename value_at, typename metric_at, typename callback_at = dummy_callback_t>
+    template <                                   //
+        typename value_at,                       //
+        typename metric_at,                      //
+        typename callback_at = dummy_callback_t, //
+        typename prefetch_at = dummy_prefetch_t  //
+        >
     add_result_t add(                                    //
         key_t key, value_at&& value, metric_at&& metric, //
-        index_add_config_t config = {}, callback_at&& callback = callback_at{}) usearch_noexcept_m {
+        index_update_config_t config = {},               //
+        callback_at&& callback = callback_at{},          //
+        prefetch_at&& prefetch = prefetch_at{}) usearch_noexcept_m {
 
         add_result_t result;
         if (is_immutable())
@@ -2034,17 +2117,17 @@ class index_gt {
         }
 
         // Pull stats
-        result.measurements = context.measurements_count;
-        result.lookups = context.iteration_cycles;
+        result.computed_distances = context.computed_distances_count;
+        result.visited_members = context.iteration_cycles;
 
-        connect_node_across_levels_(                      //
-            new_slot, value, metric,                      //
-            entry_idx_copy, max_level_copy, target_level, //
+        connect_node_across_levels_(                                //
+            value, metric, prefetch,                                //
+            new_slot, entry_idx_copy, max_level_copy, target_level, //
             config, context);
 
         // Normalize stats
-        result.measurements = context.measurements_count - result.measurements;
-        result.lookups = context.iteration_cycles - result.lookups;
+        result.computed_distances = context.computed_distances_count - result.computed_distances;
+        result.visited_members = context.iteration_cycles - result.visited_members;
 
         // Updating the entry point if needed
         if (target_level > max_level_copy) {
@@ -2054,14 +2137,8 @@ class index_gt {
         return result;
     }
 
-    enum class callback_kind_t {
-        members_k = 0,
-        slots_k,
-        slots_batch_k,
-    };
-
     /**
-     *  @brief  Update an existing entry. Thread-safe. Supports heterogeneous lookups.
+     *  @brief  Update an existing entry. Thread-safe. Supports @b heterogeneous lookups.
      *
      *  @tparam metric_at
      *      A function responsible for computing the distance @b (dis-similarity) between two objects.
@@ -2079,10 +2156,20 @@ class index_gt {
      *  @param[in] config Configuration options for this specific operation.
      *  @param[in] callback On-success callback, executed while the `member_ref_t` is still under lock.
      */
-    template <typename value_at, typename metric_at, typename callback_at = dummy_callback_t>
-    add_result_t update(                                                             //
-        member_iterator_t iterator, key_t key, value_at&& value, metric_at&& metric, //
-        index_add_config_t config = {}, callback_at&& callback = callback_at{}) usearch_noexcept_m {
+    template <                                   //
+        typename value_at,                       //
+        typename metric_at,                      //
+        typename callback_at = dummy_callback_t, //
+        typename prefetch_at = dummy_prefetch_t  //
+        >
+    add_result_t update(                        //
+        member_iterator_t iterator,             //
+        key_t key,                              //
+        value_at&& value,                       //
+        metric_at&& metric,                     //
+        index_update_config_t config = {},      //
+        callback_at&& callback = callback_at{}, //
+        prefetch_at&& prefetch = prefetch_at{}) usearch_noexcept_m {
 
         usearch_assert_m(!is_immutable(), "Can't add to an immutable index");
         add_result_t result;
@@ -2113,18 +2200,18 @@ class index_gt {
         node.level(node_level);
 
         // Pull stats
-        result.measurements = context.measurements_count;
-        result.lookups = context.iteration_cycles;
+        result.computed_distances = context.computed_distances_count;
+        result.visited_members = context.iteration_cycles;
 
-        connect_node_across_levels_(             //
-            old_slot, value, metric,             //
-            entry_slot_, max_level_, node_level, //
+        connect_node_across_levels_(                       //
+            value, metric, prefetch,                       //
+            old_slot, entry_slot_, max_level_, node_level, //
             config, context);
         node.key(key);
 
         // Normalize stats
-        result.measurements = context.measurements_count - result.measurements;
-        result.lookups = context.iteration_cycles - result.lookups;
+        result.computed_distances = context.computed_distances_count - result.computed_distances;
+        result.visited_members = context.iteration_cycles - result.visited_members;
         result.slot = old_slot;
 
         callback(at(old_slot));
@@ -2140,10 +2227,19 @@ class index_gt {
      *  @param[in] predicate Optional filtering predicate for `member_cref_t`.
      *  @return Smart object referencing temporary memory. Valid until next `search()` or `add()`.
      */
-    template <typename value_at, typename metric_at, typename predicate_at = dummy_predicate_t>
-    search_result_t search(                                     //
-        value_at query, std::size_t wanted, metric_at&& metric, //
-        index_search_config_t config = {}, predicate_at&& predicate = predicate_at{}) const noexcept {
+    template <                                     //
+        typename value_at,                         //
+        typename metric_at,                        //
+        typename predicate_at = dummy_predicate_t, //
+        typename prefetch_at = dummy_prefetch_t    //
+        >
+    search_result_t search(                        //
+        value_at&& query,                          //
+        std::size_t wanted,                        //
+        metric_at&& metric,                        //
+        index_search_config_t config = {},         //
+        predicate_at&& predicate = predicate_at{}, //
+        prefetch_at&& prefetch = prefetch_at{}) const noexcept {
 
         context_t& context = contexts_[config.thread];
         top_candidates_t& top = context.top_candidates;
@@ -2152,13 +2248,13 @@ class index_gt {
             return result;
 
         // Go down the level, tracking only the closest match
-        result.measurements = context.measurements_count;
-        result.lookups = context.iteration_cycles;
+        result.computed_distances = context.computed_distances_count;
+        result.visited_members = context.iteration_cycles;
 
         if (config.exact) {
             if (!top.reserve(wanted))
                 return result.failed("Out of memory!");
-            search_exact_(query, wanted, metric, context, std::forward<predicate_at>(predicate));
+            search_exact_(query, metric, predicate, wanted, context);
         } else {
             next_candidates_t& next = context.next_candidates;
             std::size_t expansion = (std::max)(config.expansion, wanted);
@@ -2167,10 +2263,10 @@ class index_gt {
             if (!top.reserve(expansion))
                 return result.failed("Out of memory!");
 
-            std::size_t closest_slot = search_for_one_(entry_slot_, query, metric, max_level_, 0, context);
+            std::size_t closest_slot = search_for_one_(query, metric, prefetch, entry_slot_, max_level_, 0, context);
+
             // For bottom layer we need a more optimized procedure
-            if (!search_to_find_in_base_( //
-                    closest_slot, query, metric, expansion, context, std::forward<predicate_at>(predicate)))
+            if (!search_to_find_in_base_(query, metric, predicate, prefetch, closest_slot, expansion, context))
                 return result.failed("Out of memory!");
         }
 
@@ -2178,8 +2274,8 @@ class index_gt {
         top.shrink(wanted);
 
         // Normalize stats
-        result.measurements = context.measurements_count - result.measurements;
-        result.lookups = context.iteration_cycles - result.lookups;
+        result.computed_distances = context.computed_distances_count - result.computed_distances;
+        result.visited_members = context.iteration_cycles - result.visited_members;
         result.count = top.size();
         return result;
     }
@@ -2234,6 +2330,8 @@ class index_gt {
     /**
      *  @brief  A relatively accurate lower bound on the amount of memory consumed by the system.
      *          In practice it's error will be below 10%.
+     *
+     *  @see    `stream_length` for the length of the binary serialized representation.
      */
     std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const noexcept {
         std::size_t total = 0;
@@ -2258,16 +2356,29 @@ class index_gt {
 #pragma region Serialization
 
     /**
-     *  @brief  Saves serialized binary index representation to disk,
-     *          co-locating vectors and neighbors lists.
-     *          Available on Linux, MacOS, Windows.
+     *  @brief  Saves serialized binary index representation to a file, generally on disk.
      */
     template <typename progress_at = dummy_progress_t>
     serialization_result_t save(output_file_t file, progress_at&& progress = {}) const noexcept {
 
         serialization_result_t result = file.open_if_not();
-        if (!result)
-            return result;
+        if (result)
+            stream(
+                [&](void* buffer, std::size_t length) {
+                    result = file.write(buffer, length);
+                    return !!result;
+                },
+                std::forward<progress_at>(progress));
+        return result;
+    }
+
+    /**
+     *  @brief  Saves serialized binary index representation to a stream.
+     */
+    template <typename output_callback_at, typename progress_at = dummy_progress_t>
+    serialization_result_t stream(output_callback_at&& callback, progress_at&& progress = {}) const noexcept {
+
+        serialization_result_t result;
 
         // Export some basic metadata
         index_serialized_header_t header;
@@ -2276,9 +2387,8 @@ class index_gt {
         header.connectivity_base = config_.connectivity_base;
         header.max_level = max_level_;
         header.entry_slot = entry_slot_;
-        result = file.write(&header, sizeof(header));
-        if (!result)
-            return result;
+        if (!callback(&header, sizeof(header)))
+            return result.failed("Failed to serialize into stream");
 
         // Export the number of levels per node
         // That is both enough to estimate the overall memory consumption,
@@ -2286,17 +2396,15 @@ class index_gt {
         for (std::size_t i = 0; i != header.size; ++i) {
             node_t node = node_at_(i);
             level_t level = node.level();
-            result = file.write(&level, sizeof(level));
-            if (!result)
-                return result;
+            if (!callback(&level, sizeof(level)))
+                return result.failed("Failed to serialize into stream");
         }
 
         // After that dump the nodes themselves
         for (std::size_t i = 0; i != header.size; ++i) {
             span_bytes_t node_bytes = node_bytes_(node_at_(i));
-            result = file.write(node_bytes.data(), node_bytes.size());
-            if (!result)
-                return result;
+            if (!callback(node_bytes.data(), node_bytes.size()))
+                return result.failed("Failed to serialize into stream");
             progress(i, header.size);
         }
 
@@ -2304,9 +2412,19 @@ class index_gt {
     }
 
     /**
-     *  @brief  Loads the serialized binary index representation from disk,
-     *          copying both vectors and neighbors lists into RAM.
-     *          Available on Linux, MacOS, Windows.
+     *  @brief  Estimate the binary length (in bytes) of the serialized index.
+     */
+    std::size_t stream_length() const noexcept {
+        std::size_t neighbors_length = 0;
+        for (std::size_t i = 0; i != size(); ++i)
+            neighbors_length += node_bytes_(node_at_(i).level());
+        return sizeof(index_serialized_header_t) + neighbors_length;
+    }
+
+    /**
+     *  @brief  Loads the serialized binary index representation from disk to RAM.
+     *          Adjusts the configuration properties of the constructed index to
+     *          match the settings in the file.
      */
     template <typename progress_at = dummy_progress_t>
     serialization_result_t load(input_file_t file, progress_at&& progress = {}) noexcept {
@@ -2363,8 +2481,7 @@ class index_gt {
 
     /**
      *  @brief  Memory-maps the serialized binary index representation from disk,
-     *          @b without copying the vectors and neighbors lists into RAM.
-     *          Available on Linux, MacOS, Windows.
+     *          @b without copying data into RAM, and fetching it on-demand.
      */
     template <typename progress_at = dummy_progress_t>
     serialization_result_t view(memory_mapped_file_t file, std::size_t offset = 0,
@@ -2426,6 +2543,105 @@ class index_gt {
 
 #pragma endregion
 
+    /**
+     *  @brief  Performs compaction on the whole HNSW index, purging some entries
+     *          and links to them, while also generating a more efficient mapping,
+     *          putting the more frequently used entries closer together.
+     *
+     *
+     * Scans the whole collection, removing the links leading towards
+     *          banned entries. This essentially isolates some nodes from the rest
+     *          of the graph, while keeping their outgoing links, in case the node
+     *          is structurally relevant and has a crucial role in the index.
+     *          It won't reclaim the memory.
+     *
+     *  @param[in] allow_member Predicate to mark nodes for isolation.
+     *  @param[in] executor Thread-pool to execute the job in parallel.
+     *  @param[in] progress Callback to report the execution progress.
+     */
+    template <typename values_at, typename metric_at,                       //
+              typename slot_transition_at = dummy_label_to_label_mapping_t, //
+              typename executor_at = dummy_executor_t,                      //
+              typename progress_at = dummy_progress_t,                      //
+              typename prefetch_at = dummy_prefetch_t>
+    void compact(                             //
+        values_at&& values,                   //
+        metric_at&& metric,                   //
+        slot_transition_at&& slot_transition, //
+
+        executor_at&& executor = executor_at{}, //
+        progress_at&& progress = progress_at{}, //
+        prefetch_at&& prefetch = prefetch_at{}) noexcept {
+
+        // Export all the keys, slots, and levels.
+        // Partition them with the predicate.
+        // Sort the allowed entries in descending order of their level.
+        // Create a new array mapping old slots to the new ones (INT_MAX for deleted items).
+        struct slot_level_t {
+            compressed_slot_t old_slot;
+            compressed_slot_t cluster;
+            level_t level;
+        };
+        using slot_level_allocator_t = typename dynamic_allocator_traits_t::template rebind_alloc<slot_level_t>;
+        buffer_gt<slot_level_t, slot_level_allocator_t> slots_and_levels(size());
+
+        // For every bottom level node, determine its parent cluster
+        executor.execute_bulk(slots_and_levels.size(), [&](std::size_t thread_idx, std::size_t old_slot) {
+            context_t& context = contexts_[thread_idx];
+            std::size_t cluster = search_for_one_( //
+                values[citerator_at(old_slot)],    //
+                metric, prefetch,                  //
+                entry_slot_, max_level_, 0, context);
+            slots_and_levels[old_slot] = {                                          //
+                                          static_cast<compressed_slot_t>(old_slot), //
+                                          static_cast<compressed_slot_t>(cluster),  //
+                                          node_at_(old_slot).level()};
+        });
+
+        // Where the actual permutation happens:
+        std::sort(slots_and_levels.begin(), slots_and_levels.end(), [](slot_level_t const& a, slot_level_t const& b) {
+            return a.level == b.level ? a.cluster < b.cluster : a.level > b.level;
+        });
+
+        using size_allocator_t = typename dynamic_allocator_traits_t::template rebind_alloc<std::size_t>;
+        buffer_gt<std::size_t, size_allocator_t> old_slot_to_new(slots_and_levels.size());
+        for (std::size_t new_slot = 0; new_slot != slots_and_levels.size(); ++new_slot)
+            old_slot_to_new[slots_and_levels[new_slot].old_slot] = new_slot;
+
+        // Erase all the incoming links
+        buffer_gt<node_t, nodes_allocator_t> reordered_nodes(slots_and_levels.size());
+        tape_allocator_t reordered_tape;
+
+        for (std::size_t new_slot = 0; new_slot != slots_and_levels.size(); ++new_slot) {
+            std::size_t old_slot = slots_and_levels[new_slot].old_slot;
+            node_t old_node = node_at_(old_slot);
+
+            std::size_t node_bytes = node_bytes_(old_node.level());
+            byte_t* new_data = (byte_t*)reordered_tape.allocate(node_bytes);
+            node_t new_node{new_data};
+            std::memcpy(new_data, old_node.tape(), node_bytes);
+
+            for (level_t level = 0; level <= old_node.level(); ++level)
+                for (misaligned_ref_gt<compressed_slot_t> neighbor : neighbors_(new_node, level))
+                    neighbor = static_cast<compressed_slot_t>(old_slot_to_new[compressed_slot_t(neighbor)]);
+
+            reordered_nodes[new_slot] = new_node;
+
+            progress(new_slot, slots_and_levels.size());
+        }
+
+        for (std::size_t new_slot = 0; new_slot != slots_and_levels.size(); ++new_slot) {
+            std::size_t old_slot = slots_and_levels[new_slot].old_slot;
+            slot_transition(node_at_(old_slot).ckey(),                //
+                            static_cast<compressed_slot_t>(old_slot), //
+                            static_cast<compressed_slot_t>(new_slot));
+        }
+
+        nodes_ = std::move(reordered_nodes);
+        tape_allocator_ = std::move(reordered_tape);
+        entry_slot_ = old_slot_to_new[entry_slot_];
+    }
+
     /**
      *  @brief  Scans the whole collection, removing the links leading towards
      *          banned entries. This essentially isolates some nodes from the rest
@@ -2448,7 +2664,8 @@ class index_gt {
         progress_at&& progress = progress_at{}) noexcept {
 
         // Erase all the incoming links
-        executor.execute_bulk(size(), [&](std::size_t, std::size_t node_idx) {
+        std::size_t nodes_count = size();
+        executor.execute_bulk(nodes_count, [&](std::size_t, std::size_t node_idx) {
             node_t node = node_at_(node_idx);
             for (level_t level = 0; level <= node.level(); ++level) {
                 neighbors_ref_t neighbors = neighbors_(node, level);
@@ -2461,6 +2678,7 @@ class index_gt {
                         neighbors.push_back(neighbor_slot);
                 }
             }
+            progress(node_idx, nodes_count);
         });
     }
 
@@ -2542,27 +2760,29 @@ class index_gt {
         return {nodes_mutexes_, slot};
     }
 
-    template <typename value_at, typename metric_at>
-    void connect_node_across_levels_(                                    //
-        std::size_t node_slot, value_at&& value, metric_at&& metric,     //
-        std::size_t entry_slot, level_t max_level, level_t target_level, //
-        index_add_config_t const& config, context_t& context) usearch_noexcept_m {
+    template <typename value_at, typename metric_at, typename prefetch_at>
+    void connect_node_across_levels_(                                                           //
+        value_at&& value, metric_at&& metric, prefetch_at&& prefetch,                           //
+        std::size_t node_slot, std::size_t entry_slot, level_t max_level, level_t target_level, //
+        index_update_config_t const& config, context_t& context) usearch_noexcept_m {
 
         // Go down the level, tracking only the closest match
-        std::size_t closest_slot = search_for_one_(entry_slot, value, metric, max_level, target_level, context);
+        std::size_t closest_slot = search_for_one_( //
+            value, metric, prefetch,                //
+            entry_slot, max_level, target_level, context);
 
         // From `target_level` down perform proper extensive search
         for (level_t level = (std::min)(target_level, max_level); level >= 0; --level) {
             // TODO: Handle out of memory conditions
-            search_to_insert_(closest_slot, node_slot, value, metric, level, config.expansion, context);
-            closest_slot = connect_new_node_(node_slot, level, context, metric);
-            reconnect_neighbor_nodes_(node_slot, value, level, context, metric);
+            search_to_insert_(value, metric, prefetch, closest_slot, node_slot, level, config.expansion, context);
+            closest_slot = connect_new_node_(metric, node_slot, level, context);
+            reconnect_neighbor_nodes_(metric, node_slot, value, level, context);
         }
     }
 
     template <typename metric_at>
     std::size_t connect_new_node_( //
-        std::size_t new_slot, level_t level, context_t& context, metric_at&& metric) usearch_noexcept_m {
+        metric_at&& metric, std::size_t new_slot, level_t level, context_t& context) usearch_noexcept_m {
 
         node_t new_node = node_at_(new_slot);
         top_candidates_t& top = context.top_candidates;
@@ -2571,7 +2791,7 @@ class index_gt {
         neighbors_ref_t new_neighbors = neighbors_(new_node, level);
         {
             usearch_assert_m(!new_neighbors.size(), "The newly inserted element should have blank link list");
-            candidates_view_t top_view = refine_(top, config_.connectivity, context, metric);
+            candidates_view_t top_view = refine_(metric, config_.connectivity, top, context);
 
             for (std::size_t idx = 0; idx != top_view.size(); idx++) {
                 usearch_assert_m(!new_neighbors[idx], "Possible memory corruption");
@@ -2585,8 +2805,8 @@ class index_gt {
 
     template <typename value_at, typename metric_at>
     void reconnect_neighbor_nodes_( //
-        std::size_t new_slot, value_at&& value, level_t level, context_t& context,
-        metric_at&& metric) usearch_noexcept_m {
+        metric_at&& metric, std::size_t new_slot, value_at&& value, level_t level,
+        context_t& context) usearch_noexcept_m {
 
         node_t new_node = node_at_(new_slot);
         top_candidates_t& top = context.top_candidates;
@@ -2623,7 +2843,7 @@ class index_gt {
 
             // Export the results:
             close_header.clear();
-            candidates_view_t top_view = refine_(top, connectivity_max, context, metric);
+            candidates_view_t top_view = refine_(metric, connectivity_max, top, context);
             for (std::size_t idx = 0; idx != top_view.size(); idx++)
                 close_header.push_back(top_view[idx].slot);
         }
@@ -2635,11 +2855,77 @@ class index_gt {
         return (level_t)r;
     }
 
-    template <typename value_at, typename metric_at>
-    std::size_t search_for_one_(                                        //
-        std::size_t closest_slot, value_at&& query, metric_at&& metric, //
-        level_t begin_level, level_t end_level,                         //
-        context_t& context) const noexcept {
+    struct candidates_range_t;
+    class candidates_iterator_t {
+        friend struct candidates_range_t;
+
+        index_gt const& index_;
+        neighbors_ref_t neighbors_;
+        visits_bitset_t& visits_;
+        std::size_t current_;
+
+        candidates_iterator_t& skip_missing() noexcept {
+            while (current_ != neighbors_.size()) {
+                compressed_slot_t neighbor_slot = neighbors_[current_];
+                if (visits_.test(neighbor_slot))
+                    current_++;
+                else
+                    break;
+            }
+            return *this;
+        }
+
+      public:
+        using element_t = compressed_slot_t;
+        using iterator_category = std::forward_iterator_tag;
+        using value_type = element_t;
+        using difference_type = std::ptrdiff_t;
+        using pointer = misaligned_ptr_gt<element_t>;
+        using reference = misaligned_ref_gt<element_t>;
+
+        reference operator*() const noexcept { return slot(); }
+        candidates_iterator_t(index_gt const& index, neighbors_ref_t neighbors, visits_bitset_t& visits,
+                              std::size_t progress) noexcept
+            : index_(index), neighbors_(neighbors), visits_(visits), current_(progress) {}
+        candidates_iterator_t operator++(int) noexcept {
+            return candidates_iterator_t(index_, visits_, neighbors_, current_ + 1).skip_missing();
+        }
+        candidates_iterator_t& operator++() noexcept {
+            ++current_;
+            skip_missing();
+            return *this;
+        }
+        bool operator==(candidates_iterator_t const& other) noexcept { return current_ == other.current_; }
+        bool operator!=(candidates_iterator_t const& other) noexcept { return current_ != other.current_; }
+
+        key_t key() const noexcept { return index_->node_at_(slot()).key(); }
+        compressed_slot_t slot() const noexcept { return neighbors_[current_]; }
+        friend inline std::size_t get_slot(candidates_iterator_t const& it) noexcept { return it.slot(); }
+        friend inline key_t get_key(candidates_iterator_t const& it) noexcept { return it.key(); }
+    };
+
+    struct candidates_range_t {
+        index_gt const& index;
+        neighbors_ref_t neighbors;
+        visits_bitset_t& visits;
+
+        candidates_iterator_t begin() const noexcept {
+            return candidates_iterator_t{index, neighbors, visits, 0}.skip_missing();
+        }
+        candidates_iterator_t end() const noexcept { return {index, neighbors, visits, neighbors.size()}; }
+    };
+
+    template <typename value_at, typename metric_at, typename prefetch_at = dummy_prefetch_t>
+    std::size_t search_for_one_(                                      //
+        value_at&& query, metric_at&& metric, prefetch_at&& prefetch, //
+        std::size_t closest_slot, level_t begin_level, level_t end_level, context_t& context) const noexcept {
+
+        visits_bitset_t& visits = context.visits;
+        visits.clear();
+
+        // Optional prefetching
+        if (!std::is_same<prefetch_at, dummy_prefetch_t>::value)
+            prefetch(citerator_at(closest_slot), citerator_at(closest_slot + 1));
 
         distance_t closest_dist = context.measure(query, citerator_at(closest_slot), metric);
         for (level_t level = begin_level; level > end_level; --level) {
@@ -2648,6 +2934,14 @@ class index_gt {
                 changed = false;
                 node_lock_t closest_lock = node_lock_(closest_slot);
                 neighbors_ref_t closest_neighbors = neighbors_non_base_(node_at_(closest_slot), level);
+
+                // Optional prefetching
+                if (!std::is_same<prefetch_at, dummy_prefetch_t>::value) {
+                    candidates_range_t missing_candidates{*this, closest_neighbors, visits};
+                    prefetch(missing_candidates.begin(), missing_candidates.end());
+                }
+
+                // Actual traversal
                 for (compressed_slot_t candidate_slot : closest_neighbors) {
                     distance_t candidate_dist = context.measure(query, citerator_at(candidate_slot), metric);
                     if (candidate_dist < closest_dist) {
@@ -2667,10 +2961,11 @@ class index_gt {
      *          Locks the nodes in the process, assuming other threads are updating neighbors lists.
      *  @return `true` if procedure succeeded, `false` if run out of memory.
      */
-    template <typename value_at, typename metric_at>
-    bool search_to_insert_(                                                                 //
-        std::size_t start_slot, std::size_t new_slot, value_at&& query, metric_at&& metric, //
-        level_t level, std::size_t top_limit, context_t& context) noexcept {
+    template <typename value_at, typename metric_at, typename prefetch_at = dummy_prefetch_t>
+    bool search_to_insert_(                                           //
+        value_at&& query, metric_at&& metric, prefetch_at&& prefetch, //
+        std::size_t start_slot, std::size_t new_slot, level_t level, std::size_t top_limit,
+        context_t& context) noexcept {
 
         visits_bitset_t& visits = context.visits;
         next_candidates_t& next = context.next_candidates; // pop min, push
@@ -2680,6 +2975,10 @@ class index_gt {
         next.clear();
         top.clear();
 
+        // Optional prefetching
+        if (!std::is_same<prefetch_at, dummy_prefetch_t>::value)
+            prefetch(citerator_at(start_slot), citerator_at(start_slot + 1));
+
         distance_t radius = context.measure(query, citerator_at(start_slot), metric);
         next.insert_reserved({-radius, static_cast<compressed_slot_t>(start_slot)});
         top.insert_reserved({radius, static_cast<compressed_slot_t>(start_slot)});
@@ -2701,6 +3000,12 @@ class index_gt {
             node_lock_t candidate_lock = node_lock_(candidate_slot);
             neighbors_ref_t candidate_neighbors = neighbors_(candidate_ref, level);
 
+            // Optional prefetching
+            if (!std::is_same<prefetch_at, dummy_prefetch_t>::value) {
+                candidates_range_t missing_candidates{*this, candidate_neighbors, visits};
+                prefetch(missing_candidates.begin(), missing_candidates.end());
+            }
+
             for (compressed_slot_t successor_slot : candidate_neighbors) {
                 if (visits.set(successor_slot))
                     continue;
@@ -2724,10 +3029,10 @@ class index_gt {
      *          Doesn't lock any nodes, assuming read-only simultaneous access.
      *  @return `true` if procedure succeeded, `false` if run out of memory.
      */
-    template <typename value_at, typename metric_at, typename predicate_at>
-    bool search_to_find_in_base_(                                     //
-        std::size_t start_slot, value_at&& query, metric_at&& metric, //
-        std::size_t expansion, context_t& context, predicate_at&& predicate) const noexcept {
+    template <typename value_at, typename metric_at, typename predicate_at, typename prefetch_at>
+    bool search_to_find_in_base_(                                                               //
+        value_at&& query, metric_at&& metric, predicate_at&& predicate, prefetch_at&& prefetch, //
+        std::size_t start_slot, std::size_t expansion, context_t& context) const noexcept {
 
         visits_bitset_t& visits = context.visits;
         next_candidates_t& next = context.next_candidates; // pop min, push
@@ -2738,6 +3043,10 @@ class index_gt {
         next.clear();
         top.clear();
 
+        // Optional prefetching
+        if (!std::is_same<prefetch_at, dummy_prefetch_t>::value)
+            prefetch(citerator_at(start_slot), citerator_at(start_slot + 1));
+
         distance_t radius = context.measure(query, citerator_at(start_slot), metric);
         next.insert_reserved({-radius, static_cast<compressed_slot_t>(start_slot)});
         top.insert_reserved({radius, static_cast<compressed_slot_t>(start_slot)});
@@ -2754,6 +3063,12 @@ class index_gt {
 
             neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot));
 
+            // Optional prefetching
+            if (!std::is_same<prefetch_at, dummy_prefetch_t>::value) {
+                candidates_range_t missing_candidates{*this, candidate_neighbors, visits};
+                prefetch(missing_candidates.begin(), missing_candidates.end());
+            }
+
             for (compressed_slot_t successor_slot : candidate_neighbors) {
                 if (visits.set(successor_slot))
                     continue;
@@ -2762,7 +3077,7 @@ class index_gt {
                 if (top.size() < top_limit || successor_dist < radius) {
                     // This can substantially grow our priority queue:
                     next.insert({-successor_dist, successor_slot});
-                    if (!is_dummy_predicate<predicate_at>())
+                    if (!is_dummy<predicate_at>())
                         if (!predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot}))
                             continue;
 
@@ -2776,23 +3091,19 @@ class index_gt {
         return true;
     }
 
-    template <typename predicate_at> static constexpr bool is_dummy_predicate() {
-        return std::is_same<typename std::remove_all_extents<predicate_at>::type, dummy_predicate_t>::value;
-    }
-
     /**
      *  @brief  Iterates through all members, without actually touching the index.
      */
     template <typename value_at, typename metric_at, typename predicate_at>
-    void search_exact_(                                                              //
-        value_at&& query, std::size_t count, metric_at&& metric, context_t& context, //
-        predicate_at&& predicate) const noexcept {
+    void search_exact_(                                                 //
+        value_at&& query, metric_at&& metric, predicate_at&& predicate, //
+        std::size_t count, context_t& context) const noexcept {
 
         top_candidates_t& top = context.top_candidates;
         top.clear();
         top.reserve(count);
         for (std::size_t i = 0; i != size(); ++i) {
-            if (!is_dummy_predicate<predicate_at>())
+            if (!is_dummy<predicate_at>())
                 if (!predicate(at(i)))
                     continue;
 
@@ -2808,7 +3119,8 @@ class index_gt {
      */
     template <typename metric_at>
     candidates_view_t refine_( //
-        top_candidates_t& top, std::size_t needed, context_t& context, metric_at&& metric) const noexcept {
+        metric_at&& metric,    //
+        std::size_t needed, top_candidates_t& top, context_t& context) const noexcept {
 
         top.sort_ascending();
         candidate_t* top_data = top.data();
@@ -2849,8 +3161,8 @@ struct join_result_t {
     error_t error{};
     std::size_t intersection_size{};
     std::size_t engagements{};
-    std::size_t lookups{};
-    std::size_t measurements{};
+    std::size_t visited_members{};
+    std::size_t computed_distances{};
 
     explicit operator bool() const noexcept { return !error; }
     join_result_t failed(error_t message) noexcept {
@@ -2959,8 +3271,8 @@ static join_result_t join(               //
 
     std::atomic<std::size_t> rounds{0};
     std::atomic<std::size_t> engagements{0};
-    std::atomic<std::size_t> measurements{0};
-    std::atomic<std::size_t> lookups{0};
+    std::atomic<std::size_t> computed_distances{0};
+    std::atomic<std::size_t> visited_members{0};
 
     // Concurrently process all the men
     executor.execute_bulk([&](std::size_t thread_idx) {
@@ -2993,8 +3305,8 @@ static join_result_t join(               //
             // Find the closest woman, to whom this man hasn't proposed yet.
             ++free_man_proposals;
             auto candidates = women.search(men_values[free_man_slot], free_man_proposals, women_metric, search_config);
-            lookups += candidates.lookups;
-            measurements += candidates.measurements;
+            visited_members += candidates.visited_members;
+            computed_distances += candidates.computed_distances;
             if (!candidates) {
                 // TODO:
             }
@@ -3055,8 +3367,8 @@ static join_result_t join(               //
     // Export stats
     result.engagements = engagements;
     result.intersection_size = intersection_size;
-    result.measurements = measurements;
-    result.lookups = lookups;
+    result.computed_distances = computed_distances;
+    result.visited_members = visited_members;
     return result;
 }
 
diff --git a/include/usearch/index_dense.hpp b/include/usearch/index_dense.hpp
index ae76f213..2b4fe008 100644
--- a/include/usearch/index_dense.hpp
+++ b/include/usearch/index_dense.hpp
@@ -60,7 +60,7 @@ struct index_dense_head_t {
     misaligned_ref_gt<scalar_kind_t> kind_key;
     misaligned_ref_gt<scalar_kind_t> kind_compressed_slot;
 
-    // Populational: 8 * 3 = 24 bytes
+    // Population: 8 * 3 = 24 bytes
     misaligned_ref_gt<std::uint64_t> count_present;
     misaligned_ref_gt<std::uint64_t> count_deleted;
     misaligned_ref_gt<std::uint64_t> dimensions;
@@ -114,11 +114,11 @@ struct index_dense_serialization_config_t {
     bool use_64_bit_dimensions = false;
 };
 
-struct index_dense_add_config_t : public index_add_config_t {
+struct index_dense_update_config_t : public index_update_config_t {
     bool force_vector_copy = false;
 
-    index_dense_add_config_t() = default;
-    index_dense_add_config_t(index_add_config_t base) noexcept : index_add_config_t(base) {}
+    index_dense_update_config_t() = default;
+    index_dense_update_config_t(index_update_config_t base) noexcept : index_update_config_t(base) {}
 };
 
 struct index_dense_copy_config_t : public index_copy_config_t {
@@ -171,7 +171,7 @@ inline index_dense_metadata_result_t index_dense_metadata(char const* file_path)
     if (!read)
         return result.failed(std::feof(file.get()) ? "End of file reached!" : std::strerror(errno));
 
-    // Check if the file immeditely starts with the index, instead of vectors
+    // Check if the file immediately starts with the index, instead of vectors
     result.config.exclude_vectors = true;
     if (std::memcmp(result.head_buffer, default_magic(), std::strlen(default_magic())) == 0)
         return result;
@@ -295,23 +295,24 @@ class index_dense_gt {
     mutable std::vector<byte_t> cast_buffer_;
     struct casts_t {
         cast_t from_b1x8;
-        cast_t from_f8;
+        cast_t from_i8;
         cast_t from_f16;
         cast_t from_f32;
         cast_t from_f64;
 
         cast_t to_b1x8;
-        cast_t to_f8;
+        cast_t to_i8;
         cast_t to_f16;
         cast_t to_f32;
         cast_t to_f64;
     } casts_;
 
-    /// @brief An instance of a potentially statefull `metric_t` used to initialize copies and forks.
+    /// @brief An instance of a potentially stateful `metric_t` used to initialize copies and forks.
     metric_t metric_;
 
+    using vectors_tape_allocator_t = memory_mapping_allocator_gt<8>;
     /// @brief Allocator for the copied vectors, aligned to widest double-precision scalars.
-    memory_mapping_allocator_gt<8> vectors_allocator_;
+    vectors_tape_allocator_t vectors_tape_allocator_;
 
     /// @brief For every managed `compressed_slot_t` stores a pointer to the allocated vector copy.
     mutable std::vector<byte_t*> vectors_lookup_;
@@ -353,13 +354,13 @@ class index_dense_gt {
     mutable shared_mutex_t slot_lookup_mutex_;
 
     /// @brief Ring-shaped queue of deleted entries, to be reused on future insertions.
-    ring_gt<compressed_slot_t> free_ids_;
+    ring_gt<compressed_slot_t> free_keys_;
 
-    /// @brief Mutex, controlling concurrent access to `free_ids_`.
-    mutable std::mutex free_ids_mutex_;
+    /// @brief Mutex, controlling concurrent access to `free_keys_`.
+    mutable std::mutex free_keys_mutex_;
 
     /// @brief A constant for the reserved key value, used to mark deleted entries.
-    key_t free_label_ = default_free_value<key_t>();
+    key_t free_key_ = default_free_value<key_t>();
 
   public:
     using search_result_t = typename index_t::search_result_t;
@@ -376,13 +377,13 @@ class index_dense_gt {
           casts_(std::move(other.casts_)),             //
           metric_(std::move(other.metric_)),           //
 
-          vectors_allocator_(std::move(other.vectors_allocator_)), //
-          vectors_lookup_(std::move(other.vectors_lookup_)),       //
+          vectors_tape_allocator_(std::move(other.vectors_tape_allocator_)), //
+          vectors_lookup_(std::move(other.vectors_lookup_)),                 //
 
           available_threads_(std::move(other.available_threads_)), //
           slot_lookup_(std::move(other.slot_lookup_)),             //
-          free_ids_(std::move(other.free_ids_)),                   //
-          free_label_(std::move(other.free_label_)) {}             //
+          free_keys_(std::move(other.free_keys_)),                 //
+          free_key_(std::move(other.free_key_)) {}                 //
 
     index_dense_gt& operator=(index_dense_gt&& other) {
         swap(other);
@@ -401,13 +402,13 @@ class index_dense_gt {
         std::swap(casts_, other.casts_);
         std::swap(metric_, other.metric_);
 
-        std::swap(vectors_allocator_, other.vectors_allocator_);
+        std::swap(vectors_tape_allocator_, other.vectors_tape_allocator_);
         std::swap(vectors_lookup_, other.vectors_lookup_);
 
         std::swap(available_threads_, other.available_threads_);
         std::swap(slot_lookup_, other.slot_lookup_);
-        std::swap(free_ids_, other.free_ids_);
-        std::swap(free_label_, other.free_label_);
+        std::swap(free_keys_, other.free_keys_);
+        std::swap(free_key_, other.free_key_);
     }
 
     ~index_dense_gt() {
@@ -437,7 +438,7 @@ class index_dense_gt {
         result.cast_buffer_.resize(hardware_threads * metric.bytes_per_vector());
         result.casts_ = make_casts_(scalar_kind);
         result.metric_ = metric;
-        result.free_label_ = free_label;
+        result.free_key_ = free_label;
 
         // Fill the thread IDs.
         result.available_threads_.resize(hardware_threads);
@@ -467,7 +468,7 @@ class index_dense_gt {
 
     explicit operator bool() const { return typed_; }
     std::size_t connectivity() const { return typed_->connectivity(); }
-    std::size_t size() const { return typed_->size() - free_ids_.size(); }
+    std::size_t size() const { return typed_->size() - free_keys_.size(); }
     std::size_t capacity() const { return typed_->capacity(); }
     std::size_t max_level() const noexcept { return typed_->max_level(); }
     index_dense_config_t const& config() const { return config_; }
@@ -480,7 +481,7 @@ class index_dense_gt {
     std::size_t scalar_words() const noexcept { return metric_.scalar_words(); }
     std::size_t dimensions() const noexcept { return metric_.dimensions(); }
 
-    // Fetching and changing search critereas:
+    // Fetching and changing search criteria
     std::size_t expansion_add() const { return config_.expansion_add; }
     std::size_t expansion_search() const { return config_.expansion_search; }
     void change_expansion_add(std::size_t n) { config_.expansion_add = n; }
@@ -496,41 +497,47 @@ class index_dense_gt {
     stats_t stats() const { return typed_->stats(); }
     stats_t stats(std::size_t level) const { return typed_->stats(level); }
 
+    /**
+     *  @brief  A relatively accurate lower bound on the amount of memory consumed by the system.
+     *          In practice it's error will be below 10%.
+     *
+     *  @see    `stream_length` for the length of the binary serialized representation.
+     */
     std::size_t memory_usage() const {
         return                                          //
             typed_->memory_usage(0) +                   //
             typed_->tape_allocator().total_wasted() +   //
             typed_->tape_allocator().total_reserved() + //
-            vectors_allocator_.total_allocated();
+            vectors_tape_allocator_.total_allocated();
     }
 
     // clang-format off
     add_result_t add(key_t key, b1x8_t const* vector) { return add_(key, vector, casts_.from_b1x8); }
-    add_result_t add(key_t key, f8_bits_t const* vector) { return add_(key, vector, casts_.from_f8); }
+    add_result_t add(key_t key, i8_bits_t const* vector) { return add_(key, vector, casts_.from_i8); }
     add_result_t add(key_t key, f16_t const* vector) { return add_(key, vector, casts_.from_f16); }
     add_result_t add(key_t key, f32_t const* vector) { return add_(key, vector, casts_.from_f32); }
     add_result_t add(key_t key, f64_t const* vector) { return add_(key, vector, casts_.from_f64); }
 
-    add_result_t add(key_t key, b1x8_t const* vector, index_dense_add_config_t config) { return add_(key, vector, config, casts_.from_b1x8); }
-    add_result_t add(key_t key, f8_bits_t const* vector, index_dense_add_config_t config) { return add_(key, vector, config, casts_.from_f8); }
-    add_result_t add(key_t key, f16_t const* vector, index_dense_add_config_t config) { return add_(key, vector, config, casts_.from_f16); }
-    add_result_t add(key_t key, f32_t const* vector, index_dense_add_config_t config) { return add_(key, vector, config, casts_.from_f32); }
-    add_result_t add(key_t key, f64_t const* vector, index_dense_add_config_t config) { return add_(key, vector, config, casts_.from_f64); }
+    add_result_t add(key_t key, b1x8_t const* vector, index_dense_update_config_t config) { return add_(key, vector, config, casts_.from_b1x8); }
+    add_result_t add(key_t key, i8_bits_t const* vector, index_dense_update_config_t config) { return add_(key, vector, config, casts_.from_i8); }
+    add_result_t add(key_t key, f16_t const* vector, index_dense_update_config_t config) { return add_(key, vector, config, casts_.from_f16); }
+    add_result_t add(key_t key, f32_t const* vector, index_dense_update_config_t config) { return add_(key, vector, config, casts_.from_f32); }
+    add_result_t add(key_t key, f64_t const* vector, index_dense_update_config_t config) { return add_(key, vector, config, casts_.from_f64); }
 
     search_result_t search(b1x8_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_b1x8); }
-    search_result_t search(f8_bits_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f8); }
+    search_result_t search(i8_bits_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_i8); }
     search_result_t search(f16_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f16); }
     search_result_t search(f32_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f32); }
     search_result_t search(f64_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f64); }
 
     search_result_t search(b1x8_t const* vector, std::size_t wanted, index_search_config_t config) const { return search_(vector, wanted, config, casts_.from_b1x8); }
-    search_result_t search(f8_bits_t const* vector, std::size_t wanted, index_search_config_t config) const { return search_(vector, wanted, config, casts_.from_f8); }
+    search_result_t search(i8_bits_t const* vector, std::size_t wanted, index_search_config_t config) const { return search_(vector, wanted, config, casts_.from_i8); }
     search_result_t search(f16_t const* vector, std::size_t wanted, index_search_config_t config) const { return search_(vector, wanted, config, casts_.from_f16); }
     search_result_t search(f32_t const* vector, std::size_t wanted, index_search_config_t config) const { return search_(vector, wanted, config, casts_.from_f32); }
     search_result_t search(f64_t const* vector, std::size_t wanted, index_search_config_t config) const { return search_(vector, wanted, config, casts_.from_f64); }
 
     bool get(key_t key, b1x8_t* vector) const { return get_(key, vector, casts_.to_b1x8); }
-    bool get(key_t key, f8_bits_t* vector) const { return get_(key, vector, casts_.to_f8); }
+    bool get(key_t key, i8_bits_t* vector) const { return get_(key, vector, casts_.to_i8); }
     bool get(key_t key, f16_t* vector) const { return get_(key, vector, casts_.to_f16); }
     bool get(key_t key, f32_t* vector) const { return get_(key, vector, casts_.to_f32); }
     bool get(key_t key, f64_t* vector) const { return get_(key, vector, casts_.to_f64); }
@@ -558,12 +565,12 @@ class index_dense_gt {
     void clear() {
         unique_lock_t lookup_lock(slot_lookup_mutex_);
 
-        std::unique_lock<std::mutex> free_lock(free_ids_mutex_);
+        std::unique_lock<std::mutex> free_lock(free_keys_mutex_);
         typed_->clear();
         slot_lookup_.clear();
         vectors_lookup_.clear();
-        free_ids_.clear();
-        vectors_allocator_.reset();
+        free_keys_.clear();
+        vectors_tape_allocator_.reset();
     }
 
     /**
@@ -576,13 +583,13 @@ class index_dense_gt {
     void reset() {
         unique_lock_t lookup_lock(slot_lookup_mutex_);
 
-        std::unique_lock<std::mutex> free_lock(free_ids_mutex_);
+        std::unique_lock<std::mutex> free_lock(free_keys_mutex_);
         std::unique_lock<std::mutex> available_threads_lock(available_threads_mutex_);
         typed_->reset();
         slot_lookup_.clear();
         vectors_lookup_.clear();
-        free_ids_.clear();
-        vectors_allocator_.reset();
+        free_keys_.clear();
+        vectors_tape_allocator_.reset();
         available_threads_.clear();
     }
 
@@ -590,14 +597,27 @@ class index_dense_gt {
      *  @brief Saves the index to a file.
      *  @param[in] path The path to the file.
      *  @param[in] config Configuration parameters for exports.
-     *  @return Outcome descriptor explictly convertable to boolean.
+     *  @return Outcome descriptor explicitly convertible to boolean.
      */
     serialization_result_t save(output_file_t file, serialization_config_t config = {}) const {
-
         serialization_result_t result = file.open_if_not();
-        if (!result)
-            return result;
+        if (result)
+            stream(
+                [&](void* buffer, std::size_t length) {
+                    result = file.write(buffer, length);
+                    return !!result;
+                },
+                config);
+        return result;
+    }
+
+    /**
+     *  @brief  Saves serialized binary index representation to a stream.
+     */
+    template <typename output_callback_at, typename progress_at = dummy_progress_t>
+    serialization_result_t stream(output_callback_at&& callback, serialization_config_t config = {}) const noexcept {
 
+        serialization_result_t result;
         std::uint64_t matrix_rows = 0;
         std::uint64_t matrix_cols = 0;
 
@@ -608,18 +628,16 @@ class index_dense_gt {
                 std::uint32_t dimensions[2];
                 dimensions[0] = static_cast<std::uint32_t>(typed_->size());
                 dimensions[1] = static_cast<std::uint32_t>(metric_.bytes_per_vector());
-                result = file.write(&dimensions, sizeof(dimensions));
-                if (!result)
-                    return result;
+                if (!callback(&dimensions, sizeof(dimensions)))
+                    return result.failed("Failed to serialize into stream");
                 matrix_rows = dimensions[0];
                 matrix_cols = dimensions[1];
             } else {
                 std::uint64_t dimensions[2];
                 dimensions[0] = static_cast<std::uint64_t>(typed_->size());
                 dimensions[1] = static_cast<std::uint64_t>(metric_.bytes_per_vector());
-                result = file.write(&dimensions, sizeof(dimensions));
-                if (!result)
-                    return result;
+                if (!callback(&dimensions, sizeof(dimensions)))
+                    return result.failed("Failed to serialize into stream");
                 matrix_rows = dimensions[0];
                 matrix_cols = dimensions[1];
             }
@@ -627,9 +645,8 @@ class index_dense_gt {
             // Dump the vectors one after another
             for (std::uint64_t i = 0; i != matrix_rows; ++i) {
                 byte_t* vector = vectors_lookup_[i];
-                result = file.write(vector, matrix_cols);
-                if (!result)
-                    return result;
+                if (!callback(vector, matrix_cols))
+                    return result.failed("Failed to serialize into stream");
             }
         }
 
@@ -656,20 +673,32 @@ class index_dense_gt {
             head.count_deleted = typed_->size() - size();
             head.dimensions = dimensions();
 
-            result = file.write(&buffer, sizeof(buffer));
-            if (!result)
-                return result;
+            if (!callback(&buffer, sizeof(buffer)))
+                return result.failed("Failed to serialize into stream");
         }
 
         // Save the actual proximity graph
-        return typed_->save(std::move(file));
+        return typed_->stream(std::forward<output_callback_at>(callback));
+    }
+
+    /**
+     *  @brief  Estimate the binary length (in bytes) of the serialized index.
+     */
+    std::size_t stream_length(serialization_config_t config = {}) const noexcept {
+        std::size_t dimensions_length = 0;
+        std::size_t matrix_length = 0;
+        if (!config.exclude_vectors) {
+            dimensions_length = config.use_64_bit_dimensions ? sizeof(std::uint64_t) * 2 : sizeof(std::uint32_t) * 2;
+            matrix_length = typed_->size() * metric_.bytes_per_vector();
+        }
+        return dimensions_length + matrix_length + sizeof(index_dense_head_buffer_t) + typed_->stream_length();
     }
 
     /**
      *  @brief Parses the index from file to RAM.
      *  @param[in] path The path to the file.
      *  @param[in] config Configuration parameters for imports.
-     *  @return Outcome descriptor explictly convertable to boolean.
+     *  @return Outcome descriptor explicitly convertible to boolean.
      */
     serialization_result_t load(input_file_t file, serialization_config_t config = {}) {
 
@@ -701,7 +730,7 @@ class index_dense_gt {
             // Load the vectors one after another
             vectors_lookup_.resize(matrix_rows);
             for (std::uint64_t slot = 0; slot != matrix_rows; ++slot) {
-                byte_t* vector = vectors_allocator_.allocate(matrix_cols);
+                byte_t* vector = vectors_tape_allocator_.allocate(matrix_cols);
                 result = file.read(vector, matrix_cols);
                 if (!result)
                     return result;
@@ -748,7 +777,7 @@ class index_dense_gt {
      *  @brief Parses the index from file, without loading it into RAM.
      *  @param[in] path The path to the file.
      *  @param[in] config Configuration parameters for imports.
-     *  @return Outcome descriptor explictly convertable to boolean.
+     *  @return Outcome descriptor explicitly convertible to boolean.
      */
     serialization_result_t view(memory_mapped_file_t file, std::size_t offset = 0, serialization_config_t config = {}) {
 
@@ -828,7 +857,7 @@ class index_dense_gt {
     }
 
     /**
-     *  @brief Checks if a vector with specidied key is present.
+     *  @brief Checks if a vector with specified key is present.
      *  @return `true` if the key is present in the index, `false` otherwise.
      */
     bool contains(key_t key) const {
@@ -837,10 +866,10 @@ class index_dense_gt {
     }
 
     /**
-     *  @brief Checks if a vector with specidied key is present.
-     *  @return `true` if the key is present in the index, `false` otherwise.
+     *  @brief Count the number of vectors with specified key present.
+     *  @return Zero if nothing is found, a positive integer otherwise.
      */
-    bool count(key_t key) const {
+    std::size_t count(key_t key) const {
         shared_lock_t lock(slot_lookup_mutex_);
         return slot_lookup_.count(key);
     }
@@ -873,19 +902,19 @@ class index_dense_gt {
             return result;
 
         // Grow the removed entries ring, if needed
-        std::unique_lock<std::mutex> free_lock(free_ids_mutex_);
-        if (free_ids_.size() == free_ids_.capacity())
-            if (!free_ids_.reserve((std::max<std::size_t>)(free_ids_.capacity() * 2, 64ul)))
+        std::unique_lock<std::mutex> free_lock(free_keys_mutex_);
+        if (free_keys_.size() == free_keys_.capacity())
+            if (!free_keys_.reserve((std::max<std::size_t>)(free_keys_.capacity() * 2, 64ul)))
                 return result.failed("Can't allocate memory for a free-list");
 
         // A removed entry would be:
-        // - present in `free_ids_`
+        // - present in `free_keys_`
         // - missing in the `slot_lookup_`
-        // - marked in the `typed_` index with a `free_label_`
+        // - marked in the `typed_` index with a `free_key_`
         compressed_slot_t slot = (*labeled_iterator).slot;
-        free_ids_.push(slot);
+        free_keys_.push(slot);
         slot_lookup_.erase(labeled_iterator);
-        typed_->at(slot).key = free_label_;
+        typed_->at(slot).key = free_key_;
         result.completed = true;
 
         return result;
@@ -904,11 +933,11 @@ class index_dense_gt {
 
         labeling_result_t result;
         unique_lock_t lookup_lock(slot_lookup_mutex_);
-        std::unique_lock<std::mutex> free_lock(free_ids_mutex_);
+        std::unique_lock<std::mutex> free_lock(free_keys_mutex_);
 
         // Grow the removed entries ring, if needed
         std::size_t count_requests = std::distance(labels_begin, labels_end);
-        if (!free_ids_.reserve(free_ids_.size() + count_requests))
+        if (!free_keys_.reserve(free_keys_.size() + count_requests))
             return result.failed("Can't allocate memory for a free-list");
 
         // Remove them one-by-one
@@ -919,13 +948,13 @@ class index_dense_gt {
                 continue;
 
             // A removed entry would be:
-            // - present in `free_ids_`
+            // - present in `free_keys_`
             // - missing in the `slot_lookup_`
-            // - marked in the `typed_` index with a `free_label_`
+            // - marked in the `typed_` index with a `free_key_`
             compressed_slot_t slot = (*labeled_iterator).slot;
-            free_ids_.push(slot);
+            free_keys_.push(slot);
             slot_lookup_.erase(labeled_iterator);
-            typed_->at(slot).key = free_label_;
+            typed_->at(slot).key = free_key_;
             result.completed += 1;
         }
 
@@ -998,10 +1027,10 @@ class index_dense_gt {
 
         // Export the free (removed) slot numbers
         index_dense_gt& copy = result.index;
-        if (!copy.free_ids_.reserve(free_ids_.size()))
+        if (!copy.free_keys_.reserve(free_keys_.size()))
             return result.failed(std::move(typed_result.error));
-        for (std::size_t i = 0; i != free_ids_.size(); ++i)
-            copy.free_ids_.push(free_ids_[i]);
+        for (std::size_t i = 0; i != free_keys_.size(); ++i)
+            copy.free_keys_.push(free_keys_[i]);
 
         // Allocate buffers and move the vectors themselves
         if (!config.force_vector_copy && copy.config_.exclude_vectors)
@@ -1009,7 +1038,7 @@ class index_dense_gt {
         else {
             copy.vectors_lookup_.resize(vectors_lookup_.size());
             for (std::size_t slot = 0; slot != vectors_lookup_.size(); ++slot)
-                copy.vectors_lookup_[slot] = copy.vectors_allocator_.allocate(copy.metric_.bytes_per_vector());
+                copy.vectors_lookup_[slot] = copy.vectors_tape_allocator_.allocate(copy.metric_.bytes_per_vector());
             if (std::count(copy.vectors_lookup_.begin(), copy.vectors_lookup_.end(), nullptr))
                 return result.failed("Out of memory!");
             for (std::size_t slot = 0; slot != vectors_lookup_.size(); ++slot)
@@ -1035,15 +1064,13 @@ class index_dense_gt {
 
         other.metric_ = metric_;
         other.available_threads_ = available_threads_;
-        other.free_label_ = free_label_;
+        other.free_key_ = free_key_;
 
         index_t* raw = index_allocator_t{}.allocate(1);
         if (!raw)
             return result.failed("Can't allocate the index");
 
-        new (raw) index_t(config()
-                          //, typed_->dynamic_allocator(), typed_->tape_allocator()
-        );
+        new (raw) index_t(config());
         other.typed_ = raw;
         return result;
     }
@@ -1068,11 +1095,11 @@ class index_dense_gt {
      *          `result.error` will contain an error message if an error occurred during the compaction operation.
      */
     template <typename executor_at = dummy_executor_t, typename progress_at = dummy_progress_t>
-    compaction_result_t compact(executor_at&& executor = executor_at{}, progress_at&& progress = progress_at{}) {
+    compaction_result_t isolate(executor_at&& executor = executor_at{}, progress_at&& progress = progress_at{}) {
         compaction_result_t result;
         std::atomic<std::size_t> pruned_edges;
         auto disallow = [&](member_cref_t const& member) noexcept {
-            bool freed = member.key == free_label_;
+            bool freed = member.key == free_key_;
             pruned_edges += freed;
             return freed;
         };
@@ -1081,6 +1108,43 @@ class index_dense_gt {
         return result;
     }
 
+    class values_proxy_t {
+        index_dense_gt const* index_;
+
+      public:
+        values_proxy_t(index_dense_gt const& index) noexcept : index_(&index) {}
+        byte_t const* operator[](compressed_slot_t slot) const noexcept { return index_->vectors_lookup_[slot]; }
+        byte_t const* operator[](member_citerator_t it) const noexcept { return index_->vectors_lookup_[get_slot(it)]; }
+    };
+
+    /**
+     *  @brief Performs compaction on the index, pruning links to removed entries.
+     *  @param executor The executor parallel processing. Default ::dummy_executor_t single-threaded.
+     *  @param progress The progress tracker instance to use. Default ::dummy_progress_t reports nothing.
+     *  @return The ::compaction_result_t indicating the result of the compaction operation.
+     *          `result.pruned_edges` will contain the number of edges that were removed.
+     *          `result.error` will contain an error message if an error occurred during the compaction operation.
+     */
+    template <typename executor_at = dummy_executor_t, typename progress_at = dummy_progress_t>
+    compaction_result_t compact(executor_at&& executor = executor_at{}, progress_at&& progress = progress_at{}) {
+        compaction_result_t result;
+
+        std::vector<byte_t*> new_vectors_lookup(vectors_lookup_.size());
+        vectors_tape_allocator_t new_vectors_allocator;
+
+        auto track_slot_change = [&](key_t, compressed_slot_t old_slot, compressed_slot_t new_slot) {
+            byte_t* new_vector = new_vectors_allocator.allocate(metric_.bytes_per_vector());
+            byte_t* old_vector = vectors_lookup_[old_slot];
+            std::memcpy(new_vector, old_vector, metric_.bytes_per_vector());
+            new_vectors_lookup[new_slot] = new_vector;
+        };
+        typed_->compact(values_proxy_t{*this}, metric_proxy_t{*this}, track_slot_change,
+                        std::forward<executor_at>(executor), std::forward<progress_at>(progress));
+        vectors_lookup_ = std::move(new_vectors_lookup);
+        vectors_tape_allocator_ = std::move(new_vectors_allocator);
+        return result;
+    }
+
     template <                                                     //
         typename man_to_woman_at = dummy_label_to_label_mapping_t, //
         typename woman_to_man_at = dummy_label_to_label_mapping_t, //
@@ -1095,17 +1159,10 @@ class index_dense_gt {
         executor_at&& executor = executor_at{},             //
         progress_at&& progress = progress_at{}) const {
 
-        class lookup_proxy_t {
-            index_dense_gt const* index_;
-
-          public:
-            lookup_proxy_t(index_dense_gt const& index) noexcept : index_(&index) {}
-            byte_t const* operator[](compressed_slot_t slot) const noexcept { return index_->vectors_lookup_[slot]; }
-        };
         index_dense_gt const& men = *this;
         return unum::usearch::join(                      //
             *men.typed_, *women.typed_,                  //
-            lookup_proxy_t{men}, lookup_proxy_t{women},  //
+            values_proxy_t{men}, values_proxy_t{women},  //
             metric_proxy_t{men}, metric_proxy_t{women},  //
             config,                                      //
             std::forward<man_to_woman_at>(man_to_woman), //
@@ -1137,12 +1194,12 @@ class index_dense_gt {
     }
 
     template <typename scalar_at>
-    add_result_t add_(key_t key, scalar_at const* vector, index_dense_add_config_t config, cast_t const& cast) {
+    add_result_t add_(key_t key, scalar_at const* vector, index_dense_update_config_t config, cast_t const& cast) {
 
         if (!config_.allow_key_collisions && contains(key))
             return add_result_t{}.failed("Duplicate keys not allowed in high-level wrappers");
 
-        // Cast the vector, if needed for compaatibility with `metric_`
+        // Cast the vector, if needed for compatibility with `metric_`
         bool copy_vector = !config_.exclude_vectors || config.force_vector_copy;
         byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
         {
@@ -1155,8 +1212,8 @@ class index_dense_gt {
         // Check if there are some removed entries, whose nodes we can reuse
         compressed_slot_t free_slot = default_free_value<compressed_slot_t>();
         {
-            std::unique_lock<std::mutex> lock(free_ids_mutex_);
-            free_ids_.try_pop(free_slot);
+            std::unique_lock<std::mutex> lock(free_keys_mutex_);
+            free_keys_.try_pop(free_slot);
         }
 
         // Perform the insertion or the update
@@ -1166,7 +1223,7 @@ class index_dense_gt {
             slot_lookup_.insert({key, static_cast<compressed_slot_t>(member.slot)});
             if (copy_vector) {
                 if (!reuse_node)
-                    vectors_lookup_[member.slot] = vectors_allocator_.allocate(metric_.bytes_per_vector());
+                    vectors_lookup_[member.slot] = vectors_tape_allocator_.allocate(metric_.bytes_per_vector());
                 std::memcpy(vectors_lookup_[member.slot], vector_data, metric_.bytes_per_vector());
             } else
                 vectors_lookup_[member.slot] = (byte_t*)vector_data;
@@ -1183,7 +1240,7 @@ class index_dense_gt {
         scalar_at const* vector, std::size_t wanted, //
         index_search_config_t config, cast_t const& cast) const {
 
-        // Cast the vector, if needed for compaatibility with `metric_`
+        // Cast the vector, if needed for compatibility with `metric_`
         byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
         {
             byte_t* casted_data = cast_buffer_.data() + metric_.bytes_per_vector() * config.thread;
@@ -1192,7 +1249,7 @@ class index_dense_gt {
                 vector_data = casted_data;
         }
 
-        auto allow = [=](member_cref_t const& member) noexcept { return member.key != free_label_; };
+        auto allow = [=](member_cref_t const& member) noexcept { return member.key != free_key_; };
         return typed_->search(vector_data, wanted, metric_proxy_t{*this}, config, allow);
     }
 
@@ -1208,20 +1265,20 @@ class index_dense_gt {
         std::size_t count_removed = 0;
         for (std::size_t i = 0; i != count_total; ++i) {
             member_cref_t member = typed_->at(i);
-            count_removed += member.key == free_label_;
+            count_removed += member.key == free_key_;
         }
 
-        // Pull entries fron the underlying `typed_` into either
-        // into `slot_lookup_`, or `free_ids_` if they are unused.
+        // Pull entries from the underlying `typed_` into either
+        // into `slot_lookup_`, or `free_keys_` if they are unused.
         unique_lock_t lock(slot_lookup_mutex_);
         slot_lookup_.clear();
         slot_lookup_.reserve(count_total - count_removed);
-        free_ids_.clear();
-        free_ids_.reserve(count_removed);
+        free_keys_.clear();
+        free_keys_.reserve(count_removed);
         for (std::size_t i = 0; i != typed_->size(); ++i) {
             member_cref_t member = typed_->at(i);
-            if (member.key == free_label_)
-                free_ids_.push(static_cast<compressed_slot_t>(i));
+            if (member.key == free_key_)
+                free_keys_.push(static_cast<compressed_slot_t>(i));
             else
                 slot_lookup_.insert(key_and_slot_t{key_t(member.key), static_cast<compressed_slot_t>(i)});
         }
@@ -1247,10 +1304,10 @@ class index_dense_gt {
 
     template <typename scalar_at> add_result_t add_(key_t key, scalar_at const* vector, cast_t const& cast) {
         thread_lock_t lock = thread_lock_();
-        index_dense_add_config_t add_config;
-        add_config.thread = lock.thread_id;
-        add_config.expansion = config_.expansion_add;
-        return add_(key, vector, add_config, cast);
+        index_dense_update_config_t update_config;
+        update_config.thread = lock.thread_id;
+        update_config.expansion = config_.expansion_add;
+        return add_(key, vector, update_config, cast);
     }
 
     template <typename scalar_at>
@@ -1268,13 +1325,13 @@ class index_dense_gt {
         casts_t result;
 
         result.from_b1x8 = cast_gt<b1x8_t, to_scalar_at>{};
-        result.from_f8 = cast_gt<f8_bits_t, to_scalar_at>{};
+        result.from_i8 = cast_gt<i8_bits_t, to_scalar_at>{};
         result.from_f16 = cast_gt<f16_t, to_scalar_at>{};
         result.from_f32 = cast_gt<f32_t, to_scalar_at>{};
         result.from_f64 = cast_gt<f64_t, to_scalar_at>{};
 
         result.to_b1x8 = cast_gt<to_scalar_at, b1x8_t>{};
-        result.to_f8 = cast_gt<to_scalar_at, f8_bits_t>{};
+        result.to_i8 = cast_gt<to_scalar_at, i8_bits_t>{};
         result.to_f16 = cast_gt<to_scalar_at, f16_t>{};
         result.to_f32 = cast_gt<to_scalar_at, f32_t>{};
         result.to_f64 = cast_gt<to_scalar_at, f64_t>{};
@@ -1287,7 +1344,7 @@ class index_dense_gt {
         case scalar_kind_t::f64_k: return make_casts_<f64_t>();
         case scalar_kind_t::f32_k: return make_casts_<f32_t>();
         case scalar_kind_t::f16_k: return make_casts_<f16_t>();
-        case scalar_kind_t::f8_k: return make_casts_<f8_bits_t>();
+        case scalar_kind_t::i8_k: return make_casts_<i8_bits_t>();
         case scalar_kind_t::b1x8_k: return make_casts_<b1x8_t>();
         default: return {};
         }
diff --git a/include/usearch/index_plugins.hpp b/include/usearch/index_plugins.hpp
index da46c6e2..6d7e2b04 100644
--- a/include/usearch/index_plugins.hpp
+++ b/include/usearch/index_plugins.hpp
@@ -52,7 +52,7 @@ struct uuid_t {
     std::uint8_t octets[16];
 };
 
-class f8_bits_t;
+class i8_bits_t;
 class f16_bits_t;
 
 #if USEARCH_USE_NATIVE_F16
@@ -147,8 +147,8 @@ template <typename scalar_at> scalar_kind_t scalar_kind() noexcept {
         return scalar_kind_t::f32_k;
     if (std::is_same<scalar_at, f16_t>())
         return scalar_kind_t::f16_k;
-    if (std::is_same<scalar_at, f8_bits_t>())
-        return scalar_kind_t::f8_k;
+    if (std::is_same<scalar_at, i8_bits_t>())
+        return scalar_kind_t::i8_k;
     if (std::is_same<scalar_at, u64_t>())
         return scalar_kind_t::u64_k;
     if (std::is_same<scalar_at, u32_t>())
@@ -223,7 +223,7 @@ inline std::size_t bits_per_scalar(scalar_kind_t scalar_kind) noexcept {
     case scalar_kind_t::f64_k: return 64;
     case scalar_kind_t::f32_k: return 32;
     case scalar_kind_t::f16_k: return 16;
-    case scalar_kind_t::f8_k: return 8;
+    case scalar_kind_t::i8_k: return 8;
     case scalar_kind_t::b1x8_k: return 1;
     default: return 0;
     }
@@ -234,7 +234,7 @@ inline std::size_t bits_per_scalar_word(scalar_kind_t scalar_kind) noexcept {
     case scalar_kind_t::f64_k: return 64;
     case scalar_kind_t::f32_k: return 32;
     case scalar_kind_t::f16_k: return 16;
-    case scalar_kind_t::f8_k: return 8;
+    case scalar_kind_t::i8_k: return 8;
     case scalar_kind_t::b1x8_k: return 8;
     default: return 0;
     }
@@ -245,7 +245,7 @@ inline char const* scalar_kind_name(scalar_kind_t scalar_kind) noexcept {
     case scalar_kind_t::f32_k: return "f32";
     case scalar_kind_t::f16_k: return "f16";
     case scalar_kind_t::f64_k: return "f64";
-    case scalar_kind_t::f8_k: return "f8";
+    case scalar_kind_t::i8_k: return "i8";
     case scalar_kind_t::b1x8_k: return "b1x8";
     default: return "";
     }
@@ -274,10 +274,10 @@ inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::s
         parsed.result = scalar_kind_t::f64_k;
     else if (str_equals(name, len, "f16"))
         parsed.result = scalar_kind_t::f16_k;
-    else if (str_equals(name, len, "f8"))
-        parsed.result = scalar_kind_t::f8_k;
+    else if (str_equals(name, len, "i8"))
+        parsed.result = scalar_kind_t::i8_k;
     else
-        parsed.failed("Unknown type, choose: f32, f16, f64, f8");
+        parsed.failed("Unknown type, choose: f32, f16, f64, i8");
     return parsed;
 }
 
@@ -344,7 +344,7 @@ class f16_bits_t {
     inline operator float() const noexcept { return f16_to_f32(uint16_); }
     inline explicit operator bool() const noexcept { return f16_to_f32(uint16_) > 0.5f; }
 
-    inline f16_bits_t(f8_bits_t) noexcept;
+    inline f16_bits_t(i8_bits_t) noexcept;
     inline f16_bits_t(bool v) noexcept : uint16_(f32_to_f16(v)) {}
     inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
     inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
@@ -387,7 +387,7 @@ class f16_bits_t {
  *  @brief  Numeric type for uniformly-distributed floating point
  *          values within [-1,1] range, quantized to integers [-100,100].
  */
-class f8_bits_t {
+class i8_bits_t {
     std::int8_t int8_{};
 
   public:
@@ -395,13 +395,13 @@ class f8_bits_t {
     constexpr static std::int8_t min_k = -100;
     constexpr static std::int8_t max_k = 100;
 
-    inline f8_bits_t() noexcept : int8_(0) {}
-    inline f8_bits_t(bool v) noexcept : int8_(v ? max_k : 0) {}
+    inline i8_bits_t() noexcept : int8_(0) {}
+    inline i8_bits_t(bool v) noexcept : int8_(v ? max_k : 0) {}
 
-    inline f8_bits_t(f8_bits_t&&) = default;
-    inline f8_bits_t& operator=(f8_bits_t&&) = default;
-    inline f8_bits_t(f8_bits_t const&) = default;
-    inline f8_bits_t& operator=(f8_bits_t const&) = default;
+    inline i8_bits_t(i8_bits_t&&) = default;
+    inline i8_bits_t& operator=(i8_bits_t&&) = default;
+    inline i8_bits_t(i8_bits_t const&) = default;
+    inline i8_bits_t& operator=(i8_bits_t const&) = default;
 
     inline operator float() const noexcept { return float(int8_) / divisor_k; }
     inline operator f16_t() const noexcept { return float(int8_) / divisor_k; }
@@ -412,15 +412,15 @@ class f8_bits_t {
     inline explicit operator std::int32_t() const noexcept { return int8_; }
     inline explicit operator std::int64_t() const noexcept { return int8_; }
 
-    inline f8_bits_t(f16_t v)
+    inline i8_bits_t(f16_t v)
         : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
-    inline f8_bits_t(float v)
+    inline i8_bits_t(float v)
         : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
-    inline f8_bits_t(double v)
+    inline i8_bits_t(double v)
         : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
 };
 
-inline f16_bits_t::f16_bits_t(f8_bits_t v) noexcept : f16_bits_t(float(v)) {}
+inline f16_bits_t::f16_bits_t(i8_bits_t v) noexcept : f16_bits_t(float(v)) {}
 
 /**
  *  @brief  An STL-based executor or a "thread-pool" for parallel execution.
@@ -761,7 +761,7 @@ template <> struct cast_gt<f16_bits_t, f16_bits_t> {
     bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
 };
 
-template <> struct cast_gt<f8_bits_t, f8_bits_t> {
+template <> struct cast_gt<i8_bits_t, i8_bits_t> {
     bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
 };
 
@@ -1012,11 +1012,11 @@ template <typename scalar_at = float, typename result_at = float> struct metric_
     }
 };
 
-struct cos_f8_t {
-    using scalar_t = f8_bits_t;
+struct cos_i8_t {
+    using scalar_t = i8_bits_t;
     using result_t = f32_t;
 
-    inline result_t operator()(f8_bits_t const* a, f8_bits_t const* b, std::size_t dim) const noexcept {
+    inline result_t operator()(i8_bits_t const* a, i8_bits_t const* b, std::size_t dim) const noexcept {
         std::int32_t ab{}, a2{}, b2{};
 #if USEARCH_USE_OPENMP
 #pragma omp simd reduction(+ : ab, a2, b2)
@@ -1036,11 +1036,11 @@ struct cos_f8_t {
     }
 };
 
-struct l2sq_f8_t {
-    using scalar_t = f8_bits_t;
+struct l2sq_i8_t {
+    using scalar_t = i8_bits_t;
     using result_t = f32_t;
 
-    inline result_t operator()(f8_bits_t const* a, f8_bits_t const* b, std::size_t dim) const noexcept {
+    inline result_t operator()(i8_bits_t const* a, i8_bits_t const* b, std::size_t dim) const noexcept {
         std::int32_t ab_deltas_sq{};
 #if USEARCH_USE_OPENMP
 #pragma omp simd reduction(+ : ab_deltas_sq)
@@ -1199,18 +1199,18 @@ class metric_punned_t {
         return {to_stl_<metric_cos_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
     }
 
-    static metric_punned_t cos_metric_f8_(std::size_t bytes_per_vector) {
+    static metric_punned_t cos_metric_i8_(std::size_t bytes_per_vector) {
         #if USEARCH_USE_SIMSIMD
-        if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 16 == 0) return {pun_stl_<int8_t>([=](int8_t const* a, int8_t const* b) { return simsimd_cos_i8x16_neon(a, b, bytes_per_vector); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f8_k, isa_kind_t::neon_k};
+        if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 16 == 0) return {pun_stl_<int8_t>([=](int8_t const* a, int8_t const* b) { return simsimd_cos_i8x16_neon(a, b, bytes_per_vector); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::neon_k};
         #endif
-        return {to_stl_<cos_f8_t>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f8_k, isa_kind_t::auto_k};
+        return {to_stl_<cos_i8_t>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::auto_k};
     }
 
     static metric_punned_t ip_metric_(std::size_t bytes_per_vector, scalar_kind_t scalar_kind) {        
         switch (scalar_kind) { // The two most common numeric types for the most common metric have optimized versions
         case scalar_kind_t::f32_k: return ip_metric_f32_(bytes_per_vector);
         case scalar_kind_t::f16_k: return cos_metric_f16_(bytes_per_vector); // Dot-product accumulates error, Cosine-distance normalizes it
-        case scalar_kind_t::f8_k:  return cos_metric_f8_(bytes_per_vector);
+        case scalar_kind_t::i8_k:  return cos_metric_i8_(bytes_per_vector);
         case scalar_kind_t::f64_k: return {to_stl_<metric_ip_gt<f64_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f64_k, isa_kind_t::auto_k};
         default: return {};
         }
@@ -1218,7 +1218,7 @@ class metric_punned_t {
 
     static metric_punned_t l2sq_metric_(std::size_t bytes_per_vector, scalar_kind_t scalar_kind) {
         switch (scalar_kind) {
-        case scalar_kind_t::f8_k: return {to_stl_<l2sq_f8_t>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f8_k, isa_kind_t::auto_k};
+        case scalar_kind_t::i8_k: return {to_stl_<l2sq_i8_t>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::i8_k, isa_kind_t::auto_k};
         case scalar_kind_t::f16_k: return {to_stl_<metric_l2sq_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
         case scalar_kind_t::f32_k: return {to_stl_<metric_l2sq_gt<f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f32_k, isa_kind_t::auto_k};
         case scalar_kind_t::f64_k: return {to_stl_<metric_l2sq_gt<f64_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::l2sq_k, scalar_kind_t::f64_k, isa_kind_t::auto_k};
@@ -1228,7 +1228,7 @@ class metric_punned_t {
 
     static metric_punned_t cos_metric_(std::size_t bytes_per_vector, scalar_kind_t scalar_kind) {
         switch (scalar_kind) {
-        case scalar_kind_t::f8_k: return cos_metric_f8_(bytes_per_vector);
+        case scalar_kind_t::i8_k: return cos_metric_i8_(bytes_per_vector);
         case scalar_kind_t::f16_k: return cos_metric_f16_(bytes_per_vector);
         case scalar_kind_t::f32_k: return {to_stl_<metric_cos_gt<f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f32_k, isa_kind_t::auto_k};
         case scalar_kind_t::f64_k: return {to_stl_<metric_cos_gt<f64_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f64_k, isa_kind_t::auto_k};
@@ -1239,7 +1239,7 @@ class metric_punned_t {
     static metric_punned_t haversine_metric_(scalar_kind_t scalar_kind) {
         std::size_t bytes_per_vector = 2u * bits_per_scalar(scalar_kind) / CHAR_BIT;
         switch (scalar_kind) {
-        case scalar_kind_t::f8_k: return {to_stl_<metric_haversine_gt<f8_bits_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::haversine_k, scalar_kind_t::f8_k, isa_kind_t::auto_k};
+        case scalar_kind_t::i8_k: return {to_stl_<metric_haversine_gt<i8_bits_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::haversine_k, scalar_kind_t::i8_k, isa_kind_t::auto_k};
         case scalar_kind_t::f16_k: return {to_stl_<metric_haversine_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::haversine_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
         case scalar_kind_t::f32_k: return {to_stl_<metric_haversine_gt<f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::haversine_k, scalar_kind_t::f32_k, isa_kind_t::auto_k};
         case scalar_kind_t::f64_k: return {to_stl_<metric_haversine_gt<f64_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::haversine_k, scalar_kind_t::f64_k, isa_kind_t::auto_k};
@@ -1249,7 +1249,7 @@ class metric_punned_t {
 
     static metric_punned_t pearson_metric_(std::size_t bytes_per_vector, scalar_kind_t scalar_kind) {
         switch (scalar_kind) {
-        case scalar_kind_t::f8_k: return {to_stl_<metric_pearson_gt<f8_bits_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::pearson_k, scalar_kind_t::f8_k, isa_kind_t::auto_k};
+        case scalar_kind_t::i8_k: return {to_stl_<metric_pearson_gt<i8_bits_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::pearson_k, scalar_kind_t::i8_k, isa_kind_t::auto_k};
         case scalar_kind_t::f16_k: return {to_stl_<metric_pearson_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::pearson_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
         case scalar_kind_t::f32_k: return {to_stl_<metric_pearson_gt<f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::pearson_k, scalar_kind_t::f32_k, isa_kind_t::auto_k};
         case scalar_kind_t::f64_k: return {to_stl_<metric_pearson_gt<f64_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::pearson_k, scalar_kind_t::f64_k, isa_kind_t::auto_k};
diff --git a/javascript/README.md b/javascript/README.md
index 46166293..3d15f205 100644
--- a/javascript/README.md
+++ b/javascript/README.md
@@ -3,7 +3,7 @@
 ## Installation
 
 USearch is available both for Node.js backend runtime and WASM frontend runtime.
-For first use the conventional `npm install`:
+For the first option, use the conventional `npm install`:
 
 ```sh
 npm install usearch
@@ -18,11 +18,11 @@ wasmer install unum/usearch
 ## Quickstart
 
 ```js
-var index = new usearch.Index({ metric: 'cos', connectivity: 16, dimensions: 3 })
-index.add(42, new Float32Array([0.2, 0.6, 0.4]))
-var results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10)
+var index = new usearch.Index({ metric: 'cos', connectivity: 16n, dimensions: 3n })
+index.add(42n, new Float32Array([0.2, 0.6, 0.4]))
+var results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10n)
 
-assert.equal(index.size(), 1)
+assert.equal(index.size(), 1n)
 assert.deepEqual(results.keys, new BigUint64Array([42n]))
 assert.deepEqual(results.distances, new Float32Array([0]))
 ```
diff --git a/javascript/lib.cpp b/javascript/lib.cpp
index a01140a7..5034dd5f 100644
--- a/javascript/lib.cpp
+++ b/javascript/lib.cpp
@@ -74,8 +74,10 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
         return;
     }
 
+    bool lossless = true;
     Napi::Object params = ctx[0].As<Napi::Object>();
-    std::size_t dimensions = params.Has("dimensions") ? params.Get("dimensions").As<Napi::Number>().Uint32Value() : 0;
+    std::size_t dimensions =
+        params.Has("dimensions") ? params.Get("dimensions").As<Napi::BigInt>().Uint64Value(&lossless) : 0;
 
     index_limits_t limits;
     std::size_t connectivity = default_connectivity();
@@ -83,13 +85,17 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
     std::size_t expansion_search = default_expansion_search();
 
     if (params.Has("capacity"))
-        limits.members = params.Get("capacity").As<Napi::Number>().Uint32Value();
+        limits.members = params.Get("capacity").As<Napi::BigInt>().Uint64Value(&lossless);
     if (params.Has("connectivity"))
-        connectivity = params.Get("connectivity").As<Napi::Number>().Uint32Value();
+        connectivity = params.Get("connectivity").As<Napi::BigInt>().Uint64Value(&lossless);
     if (params.Has("expansion_add"))
-        expansion_add = params.Get("expansion_add").As<Napi::Number>().Uint32Value();
+        expansion_add = params.Get("expansion_add").As<Napi::BigInt>().Uint64Value(&lossless);
     if (params.Has("expansion_search"))
-        expansion_search = params.Get("expansion_search").As<Napi::Number>().Uint32Value();
+        expansion_search = params.Get("expansion_search").As<Napi::BigInt>().Uint64Value(&lossless);
+    if (!lossless) {
+        Napi::TypeError::New(env, "Arguments must be unsigned integers").ThrowAsJavaScriptException();
+        return;
+    }
 
     scalar_kind_t quantization = scalar_kind_t::f32_k;
     if (params.Has("quantization")) {
@@ -121,14 +127,16 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
 }
 
 Napi::Value Index::GetDimensions(Napi::CallbackInfo const& ctx) {
-    return Napi::Number::New(ctx.Env(), native_->dimensions());
+    return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->dimensions()));
+}
+Napi::Value Index::GetSize(Napi::CallbackInfo const& ctx) {
+    return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->size()));
 }
-Napi::Value Index::GetSize(Napi::CallbackInfo const& ctx) { return Napi::Number::New(ctx.Env(), native_->size()); }
 Napi::Value Index::GetConnectivity(Napi::CallbackInfo const& ctx) {
-    return Napi::Number::New(ctx.Env(), native_->connectivity());
+    return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->connectivity()));
 }
 Napi::Value Index::GetCapacity(Napi::CallbackInfo const& ctx) {
-    return Napi::Number::New(ctx.Env(), native_->capacity());
+    return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->capacity()));
 }
 
 void Index::Save(Napi::CallbackInfo const& ctx) {
@@ -191,8 +199,12 @@ void Index::Add(Napi::CallbackInfo const& ctx) {
     using key_t = typename index_dense_t::key_t;
     std::size_t index_dimensions = native_->dimensions();
 
-    auto add = [&](Napi::Number key_js, Napi::Float32Array vector_js) {
-        key_t key = key_js.Uint32Value();
+    auto add = [&](Napi::BigInt key_js, Napi::Float32Array vector_js) {
+        bool lossless = true;
+        key_t key = static_cast<key_t>(key_js.Uint64Value(&lossless));
+        if (!lossless)
+            return Napi::TypeError::New(env, "Keys must be unsigned integers").ThrowAsJavaScriptException();
+
         float const* vector = vector_js.Data();
         std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
 
@@ -223,13 +235,13 @@ void Index::Add(Napi::CallbackInfo const& ctx) {
         for (std::size_t i = 0; i < length; i++) {
             Napi::Value key_js = keys_js[i];
             Napi::Value vector_js = vectors_js[i];
-            add(key_js.As<Napi::Number>(), vector_js.As<Napi::Float32Array>());
+            add(key_js.As<Napi::BigInt>(), vector_js.As<Napi::Float32Array>());
         }
 
-    } else if (ctx[0].IsNumber() && ctx[1].IsTypedArray()) {
+    } else if (ctx[0].IsBigInt() && ctx[1].IsTypedArray()) {
         if (native_->size() + 1 >= native_->capacity())
             native_->reserve(ceil2(native_->size() + 1));
-        add(ctx[0].As<Napi::Number>(), ctx[1].As<Napi::Float32Array>());
+        add(ctx[0].As<Napi::BigInt>(), ctx[1].As<Napi::Float32Array>());
     } else
         return Napi::TypeError::New(env, "Invalid argument type, expects integral key(s) and float vector(s)")
             .ThrowAsJavaScriptException();
@@ -237,31 +249,38 @@ void Index::Add(Napi::CallbackInfo const& ctx) {
 
 Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
     Napi::Env env = ctx.Env();
-    if (ctx.Length() < 2 || !ctx[0].IsTypedArray() || !ctx[1].IsNumber()) {
+    if (ctx.Length() < 2 || !ctx[0].IsTypedArray() || !ctx[1].IsBigInt()) {
         Napi::TypeError::New(env, "Expects a  and the number of wanted results").ThrowAsJavaScriptException();
         return {};
     }
 
     Napi::Float32Array vector_js = ctx[0].As<Napi::Float32Array>();
-    Napi::Number wanted_js = ctx[1].As<Napi::Number>();
+    Napi::BigInt wanted_js = ctx[1].As<Napi::BigInt>();
 
     float const* vector = vector_js.Data();
     std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
-    std::uint32_t wanted = wanted_js.Uint32Value();
     if (dimensions != native_->dimensions()) {
         Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
         return {};
     }
 
+    bool lossless = true;
+    std::uint64_t wanted = wanted_js.Uint64Value(&lossless);
+    if (!lossless) {
+        Napi::TypeError::New(env, "Wanted number of matches must be an unsigned integer").ThrowAsJavaScriptException();
+        return {};
+    }
+
     using key_t = typename index_dense_t::key_t;
     Napi::TypedArrayOf<key_t> matches_js = Napi::TypedArrayOf<key_t>::New(env, wanted);
+    static_assert(std::is_same<std::uint64_t, key_t>::value, "Matches.key interface expects BigUint64Array");
     Napi::Float32Array distances_js = Napi::Float32Array::New(env, wanted);
     try {
-        std::size_t count = native_->search(vector, wanted).dump_to(matches_js.Data(), distances_js.Data());
+        std::uint64_t count = native_->search(vector, wanted).dump_to(matches_js.Data(), distances_js.Data());
         Napi::Object result_js = Napi::Object::New(env);
         result_js.Set("keys", matches_js);
         result_js.Set("distances", distances_js);
-        result_js.Set("count", Napi::Number::New(env, count));
+        result_js.Set("count", Napi::BigInt::New(env, count));
         return result_js;
     } catch (std::bad_alloc const&) {
         Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
diff --git a/javascript/usearch.d.ts b/javascript/usearch.d.ts
index 6b5dd6ca..50cb411a 100644
--- a/javascript/usearch.d.ts
+++ b/javascript/usearch.d.ts
@@ -1,12 +1,12 @@
 
 /** Search result object. */
-export interface SearchResults {
-    /** The labels of the nearest neighbors found, size n*k. */
-    labels: BigUint64Array,
-    /** The disances of the nearest negihbors found, size n*k. */
+export interface Matches {
+    /** The keys of the nearest neighbors found, size n*k. */
+    keys: BigUint64Array,
+    /** The distances of the nearest neighbors found, size n*k. */
     distances: Float32Array,
-    /** The disances of the nearest negihbors found, size n*k. */
-    count: number
+    /** The distances of the nearest neighbors found, size n*k. */
+    count: bigint
 }
 
 /** K-Approximate Nearest Neighbors search index. */
@@ -15,39 +15,39 @@ export class Index {
     /**
      * Constructs a new index.
      * 
-     * @param {number} dimensions
+     * @param {bigint} dimensions
      * @param {string} metric
      * @param {string} quantization
-     * @param {number} capacity
-     * @param {number} connectivity
-     * @param {number} expansion_add
-     * @param {number} expansion_search
+     * @param {bigint} capacity
+     * @param {bigint} connectivity
+     * @param {bigint} expansion_add
+     * @param {bigint} expansion_search
      */
     constructor(...args);
 
     /**
      * Returns the dimensionality of vectors.
-     * @return {number} The dimensionality of vectors.
+     * @return {bigint} The dimensionality of vectors.
      */
-    dimensions(): number;
+    dimensions(): bigint;
 
     /**
-     * Returns the number of vectors currently indexed.
-     * @return {number} The number of vectors currently indexed.
+     * Returns the bigint of vectors currently indexed.
+     * @return {bigint} The bigint of vectors currently indexed.
      */
-    size(): number;
+    size(): bigint;
 
     /**
      * Returns index capacity.
-     * @return {numbers} The capacity of index.
+     * @return {bigints} The capacity of index.
      */
-    capacity(): number;
+    capacity(): bigint;
     
     /**
      * Returns connectivity.
-     * @return {number} The connectivity of index.
+     * @return {bigint} The connectivity of index.
      */
-    connectivity(): number;
+    connectivity(): bigint;
 
     /** 
      * Write index to a file.
@@ -70,18 +70,18 @@ export class Index {
     /** 
      * Add n vectors of dimension d to the index.
      * 
-     * @param {number | number[]} keys Input identifiers for every vector.
+     * @param {bigint | bigint[]} keys Input identifiers for every vector.
      * @param {Float32Array | Float32Array[]} mat Input matrix, matrix of size n * d.
      */
-    add(keys: number | number[], mat: Float32Array | Float32Array[]): void;
+    add(keys: bigint | bigint[], mat: Float32Array | Float32Array[]): void;
 
     /** 
      * Query n vectors of dimension d to the index. Return at most k vectors for each. 
      * If there are not enough results for a query, the result array is padded with -1s.
      *
      * @param {Float32Array} mat Input vectors to search, matrix of size n * d.
-     * @param {number} k The number of nearest neighbors to search for.
-     * @return {SearchResults} Output of the search result.
+     * @param {bigint} k The bigint of nearest neighbors to search for.
+     * @return {Matches} Output of the search result.
      */
-    search(mat: Float32Array, k: number): SearchResults;
+    search(mat: Float32Array, k: bigint): Matches;
 }
\ No newline at end of file
diff --git a/objc/USearchObjective.mm b/objc/USearchObjective.mm
index 2ca6f2a8..bc2160bf 100644
--- a/objc/USearchObjective.mm
+++ b/objc/USearchObjective.mm
@@ -52,8 +52,8 @@ metric_kind_t to_native_metric(USearchMetric m) {
 
 scalar_kind_t to_native_scalar(USearchScalar m) {
     switch (m) {
-        case USearchScalarF8:
-            return scalar_kind_t::f8_k;
+        case USearchScalarI8:
+            return scalar_kind_t::i8_k;
 
         case USearchScalarF16:
             return scalar_kind_t::f16_k;
diff --git a/objc/include/USearchObjective.h b/objc/include/USearchObjective.h
index a690ef9c..62b898c1 100644
--- a/objc/include/USearchObjective.h
+++ b/objc/include/USearchObjective.h
@@ -8,7 +8,7 @@ typedef NS_ENUM(NSUInteger, USearchScalar) {
     USearchScalarF32,
     USearchScalarF16,
     USearchScalarF64,
-    USearchScalarF8,
+    USearchScalarI8,
     USearchScalarB1
 };
 
diff --git a/python/README.md b/python/README.md
index 7578df3b..3c494678 100644
--- a/python/README.md
+++ b/python/README.md
@@ -15,7 +15,7 @@ from usearch.index import Index
 index = Index(
     ndim=3, # Define the number of dimensions in input vectors
     metric='cos', # Choose 'l2sq', 'haversine' or other metric, default = 'ip'
-    dtype='f32', # Quantize to 'f16' or 'f8' if needed, default = 'f32'
+    dtype='f32', # Quantize to 'f16' or 'i8' if needed, default = 'f32'
     connectivity=16, # How frequent should the connections in the graph be, optional
     expansion_add=128, # Control the recall of indexing, optional
     expansion_search=64, # Control the quality of search, optional
@@ -268,10 +268,15 @@ One may often want to evaluate the quality of the constructed index before runni
 The trivial way is to measure `recall@1` on the entries already present in the index.
 
 ```py
-from usearch.eval import recall_members
+from usearch.eval import self_recall
 
-assert recall_members(index, exact=True) == (1, 0)
-print(recall_members(index, exact=False))
+stats: SearchStats = self_recall(index, exact=True)
+assert stats.visited_members == 0, "Exact search won't attend index nodes"
+assert stats.computed_distances == len(index), "And will compute the distance to every node"
+
+stats: SearchStats = self_recall(index, exact=False)
+assert stats.visited_members > 0
+assert stats.computed_distances <= len(index)
 ```
 
 In case you have some ground-truth data for more than one entry, you compare search results against expected values:
diff --git a/python/lib.cpp b/python/lib.cpp
index 87828989..41a73e60 100644
--- a/python/lib.cpp
+++ b/python/lib.cpp
@@ -129,11 +129,13 @@ metric_t typed_udf(
     return metric_t(stl_function, dimensions, kind, scalar_kind);
 }
 
-metric_t udf(metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr, //
-             scalar_kind_t scalar_kind, std::size_t dimensions) {
+metric_t udf(                                                                        //
+    metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr, //
+    scalar_kind_t scalar_kind, std::size_t dimensions) {
+
     switch (scalar_kind) {
     case scalar_kind_t::b1x8_k: return typed_udf<b1x8_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
-    case scalar_kind_t::f8_k: return typed_udf<f8_bits_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
+    case scalar_kind_t::i8_k: return typed_udf<i8_bits_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
     case scalar_kind_t::f16_k: return typed_udf<f16_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
     case scalar_kind_t::f32_k: return typed_udf<f32_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
     case scalar_kind_t::f64_k: return typed_udf<f64_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
@@ -164,12 +166,12 @@ scalar_kind_t numpy_string_to_kind(std::string const& name) {
     if (name == "B" || name == "<B" || name == "u1" || name == "|u1")
         return scalar_kind_t::b1x8_k;
     else if (name == "b" || name == "<b" || name == "i1" || name == "|i1")
-        return scalar_kind_t::f8_k;
+        return scalar_kind_t::i8_k;
     else if (name == "e" || name == "<e" || name == "f2" || name == "<f2")
         return scalar_kind_t::f16_k;
     else if (name == "f" || name == "<f" || name == "f4" || name == "<f4")
         return scalar_kind_t::f32_k;
-    else if (name == "d" || name == "<d" || name == "f8" || name == "<f8")
+    else if (name == "d" || name == "<d" || name == "i8" || name == "<i8")
         return scalar_kind_t::f64_k;
     else
         return scalar_kind_t::unknown_k;
@@ -186,7 +188,7 @@ static void add_typed_to_index(                                            //
     byte_t const* keys_data = reinterpret_cast<byte_t const*>(keys_info.ptr);
 
     executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
-        index_dense_add_config_t config;
+        index_dense_update_config_t config;
         config.force_vector_copy = force_copy;
         config.thread = thread_idx;
         key_t key = *reinterpret_cast<key_t const*>(keys_data + task_idx * keys_info.strides[0]);
@@ -232,7 +234,7 @@ static void add_many_to_index(                            //
     // clang-format off
     switch (numpy_string_to_kind(vectors_info.format)) {
     case scalar_kind_t::b1x8_k: add_typed_to_index<b1x8_t>(index, keys_info, vectors_info, force_copy, threads); break;
-    case scalar_kind_t::f8_k: add_typed_to_index<f8_bits_t>(index, keys_info, vectors_info, force_copy, threads); break;
+    case scalar_kind_t::i8_k: add_typed_to_index<i8_bits_t>(index, keys_info, vectors_info, force_copy, threads); break;
     case scalar_kind_t::f16_k: add_typed_to_index<f16_t>(index, keys_info, vectors_info, force_copy, threads); break;
     case scalar_kind_t::f32_k: add_typed_to_index<f32_t>(index, keys_info, vectors_info, force_copy, threads); break;
     case scalar_kind_t::f64_k: add_typed_to_index<f64_t>(index, keys_info, vectors_info, force_copy, threads); break;
@@ -246,7 +248,7 @@ static void search_typed(                                   //
     dense_index_py_t& index, py::buffer_info& vectors_info, //
     std::size_t wanted, bool exact, std::size_t threads,    //
     py::array_t<key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py,
-    std::atomic<std::size_t>& stats_lookups, std::atomic<std::size_t>& stats_measurements) {
+    std::atomic<std::size_t>& stats_visited_members, std::atomic<std::size_t>& stats_computed_distances) {
 
     auto keys_py2d = keys_py.template mutable_unchecked<2>();
     auto distances_py2d = distances_py.template mutable_unchecked<2>();
@@ -270,8 +272,8 @@ static void search_typed(                                   //
         counts_py1d(task_idx) =
             static_cast<Py_ssize_t>(result.dump_to(&keys_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
 
-        stats_lookups += result.lookups;
-        stats_measurements += result.measurements;
+        stats_visited_members += result.visited_members;
+        stats_computed_distances += result.computed_distances;
         if (PyErr_CheckSignals() != 0)
             throw py::error_already_set();
     });
@@ -282,7 +284,7 @@ static void search_typed(                                       //
     dense_indexes_py_t& indexes, py::buffer_info& vectors_info, //
     std::size_t wanted, bool exact, std::size_t threads,        //
     py::array_t<key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py,
-    std::atomic<std::size_t>& stats_lookups, std::atomic<std::size_t>& stats_measurements) {
+    std::atomic<std::size_t>& stats_visited_members, std::atomic<std::size_t>& stats_computed_distances) {
 
     auto keys_py2d = keys_py.template mutable_unchecked<2>();
     auto distances_py2d = distances_py.template mutable_unchecked<2>();
@@ -324,8 +326,8 @@ static void search_typed(                                       //
                     wanted));
             }
 
-            stats_lookups += result.lookups;
-            stats_measurements += result.measurements;
+            stats_visited_members += result.visited_members;
+            stats_computed_distances += result.computed_distances;
             if (PyErr_CheckSignals() != 0)
                 throw py::error_already_set();
         }
@@ -363,16 +365,16 @@ static py::tuple search_many_in_index( //
     py::array_t<key_t> keys_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
     py::array_t<distance_t> distances_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
     py::array_t<Py_ssize_t> counts_py(vectors_count);
-    std::atomic<std::size_t> stats_lookups(0);
-    std::atomic<std::size_t> stats_measurements(0);
+    std::atomic<std::size_t> stats_visited_members(0);
+    std::atomic<std::size_t> stats_computed_distances(0);
 
     // clang-format off
     switch (numpy_string_to_kind(vectors_info.format)) {
-    case scalar_kind_t::b1x8_k: search_typed<b1x8_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_lookups, stats_measurements); break;
-    case scalar_kind_t::f8_k: search_typed<f8_bits_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_lookups, stats_measurements); break;
-    case scalar_kind_t::f16_k: search_typed<f16_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_lookups, stats_measurements); break;
-    case scalar_kind_t::f32_k: search_typed<f32_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_lookups, stats_measurements); break;
-    case scalar_kind_t::f64_k: search_typed<f64_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_lookups, stats_measurements); break;
+    case scalar_kind_t::b1x8_k: search_typed<b1x8_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
+    case scalar_kind_t::i8_k: search_typed<i8_bits_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
+    case scalar_kind_t::f16_k: search_typed<f16_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
+    case scalar_kind_t::f32_k: search_typed<f32_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
+    case scalar_kind_t::f64_k: search_typed<f64_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances); break;
     default: throw std::invalid_argument("Incompatible scalars in the query matrix: " + vectors_info.format);
     }
     // clang-format on
@@ -381,8 +383,8 @@ static py::tuple search_many_in_index( //
     results[0] = keys_py;
     results[1] = distances_py;
     results[2] = counts_py;
-    results[3] = stats_lookups.load();
-    results[4] = stats_measurements.load();
+    results[3] = stats_visited_members.load();
+    results[4] = stats_computed_distances.load();
     return results;
 }
 
@@ -391,21 +393,18 @@ static std::unordered_map<key_t, key_t> join_index(       //
     std::size_t max_proposals, bool exact) {
 
     std::unordered_map<key_t, key_t> a_to_b;
+    dummy_label_to_label_mapping_t b_to_a;
     a_to_b.reserve((std::min)(a.size(), b.size()));
 
-    // index_join_config_t config;
-
-    // config.max_proposals = max_proposals;
-    // config.exact = exact;
-    // config.expansion = (std::max)(a.expansion_search(), b.expansion_search());
-    // std::size_t threads = (std::min)(a.limits().threads(), b.limits().threads());
-    // executor_default_t executor{threads};
-    // join_result_t result = dense_index_py_t::join( //
-    //     a, b, config,                              //
-    //     a_to_b,                                    //
-    //     dummy_label_to_label_mapping_t{},          //
-    //     executor);
-    // result.error.raise();
+    index_join_config_t config;
+    config.max_proposals = max_proposals;
+    config.exact = exact;
+    config.expansion = (std::max)(a.expansion_search(), b.expansion_search());
+    std::size_t threads = (std::min)(a.limits().threads(), b.limits().threads());
+    executor_default_t executor{threads};
+    join_result_t result = a.join(b, config, a_to_b, b_to_a, executor);
+    result.error.raise();
+
     return a_to_b;
 }
 
@@ -418,6 +417,16 @@ static dense_index_py_t copy_index(dense_index_py_t const& index) {
     return std::move(result.index);
 }
 
+static void compact_index(dense_index_py_t& index, std::size_t threads) {
+
+    if (!threads)
+        threads = std::thread::hardware_concurrency();
+    if (!index.reserve(index_limits_t(index.size(), threads)))
+        throw std::invalid_argument("Out of memory!");
+
+    index.compact(executor_default_t{threads});
+}
+
 // clang-format off
 template <typename index_at> void save_index(index_at const& index, std::string const& path) { index.save(path.c_str()).error.raise(); }
 template <typename index_at> void load_index(index_at& index, std::string const& path) { index.load(path.c_str()).error.raise(); }
@@ -445,8 +454,8 @@ template <typename index_at> py::object get_member(index_at const& index, key_t
         return get_typed_member<f64_t>(index, key);
     else if (scalar_kind == scalar_kind_t::f16_k)
         return get_typed_member<f16_t, std::uint16_t>(index, key);
-    else if (scalar_kind == scalar_kind_t::f8_k)
-        return get_typed_member<f8_bits_t, std::int8_t>(index, key);
+    else if (scalar_kind == scalar_kind_t::i8_k)
+        return get_typed_member<i8_bits_t, std::int8_t>(index, key);
     else if (scalar_kind == scalar_kind_t::b1x8_k)
         return get_typed_member<b1x8_t, std::uint8_t>(index, key);
     else
@@ -495,15 +504,20 @@ PYBIND11_MODULE(compiled, m) {
 
     py::enum_<metric_kind_t>(m, "MetricKind")
         .value("Unknown", metric_kind_t::unknown_k)
+
         .value("IP", metric_kind_t::ip_k)
         .value("Cos", metric_kind_t::cos_k)
         .value("L2sq", metric_kind_t::l2sq_k)
+
         .value("Haversine", metric_kind_t::haversine_k)
         .value("Pearson", metric_kind_t::pearson_k)
         .value("Jaccard", metric_kind_t::jaccard_k)
         .value("Hamming", metric_kind_t::hamming_k)
         .value("Tanimoto", metric_kind_t::tanimoto_k)
-        .value("Sorensen", metric_kind_t::sorensen_k);
+        .value("Sorensen", metric_kind_t::sorensen_k)
+
+        .value("Cosine", metric_kind_t::cos_k)
+        .value("InnerProduct", metric_kind_t::ip_k);
 
     py::enum_<scalar_kind_t>(m, "ScalarKind")
         .value("Unknown", scalar_kind_t::unknown_k)
@@ -601,7 +615,7 @@ PYBIND11_MODULE(compiled, m) {
             if (!index.reserve(index_limits_t(index.size(), threads)))
                 throw std::invalid_argument("Out of memory!");
 
-            index.compact(executor_default_t{threads});
+            index.isolate(executor_default_t{threads});
             return result.completed;
         },
         py::arg("key"), py::arg("compact"), py::arg("threads"));
@@ -619,7 +633,7 @@ PYBIND11_MODULE(compiled, m) {
             if (!index.reserve(index_limits_t(index.size(), threads)))
                 throw std::invalid_argument("Out of memory!");
 
-            index.compact(executor_default_t{threads});
+            index.isolate(executor_default_t{threads});
             return result.completed;
         },
         py::arg("key"), py::arg("compact"), py::arg("threads"));
@@ -651,6 +665,7 @@ PYBIND11_MODULE(compiled, m) {
     i.def("reset", &reset_index<dense_index_py_t>, py::call_guard<py::gil_scoped_release>());
     i.def("clear", &clear_index<dense_index_py_t>, py::call_guard<py::gil_scoped_release>());
     i.def("copy", &copy_index, py::call_guard<py::gil_scoped_release>());
+    i.def("compact", &compact_index, py::call_guard<py::gil_scoped_release>());
     i.def("join", &join_index, py::arg("other"), py::arg("max_proposals") = 0, py::arg("exact") = false,
           py::call_guard<py::gil_scoped_release>());
 
diff --git a/python/scripts/bench.py b/python/scripts/bench.py
index 4ecc7335..cac7a8e7 100644
--- a/python/scripts/bench.py
+++ b/python/scripts/bench.py
@@ -26,7 +26,7 @@ def bench_speed(
     # Build various indexes:
     indexes = []
     jit_options = [False, True] if jit else [False]
-    dtype_options = [ScalarKind.F32, ScalarKind.F16, ScalarKind.F8]
+    dtype_options = [ScalarKind.F32, ScalarKind.F16, ScalarKind.I8]
     for jit, dtype in itertools.product(jit_options, dtype_options):
         metric = MetricKind.IP
         if jit:
diff --git a/python/usearch/eval.py b/python/usearch/eval.py
index 6cea072d..c4c884bd 100644
--- a/python/usearch/eval.py
+++ b/python/usearch/eval.py
@@ -55,24 +55,51 @@ def random_vectors(
         return x
 
 
-def recall_members(index: Index, sample: float = 1, **kwargs) -> Tuple[float, float]:
-    """Simplest benchmark for a quality of search, which queries every
-    existing member of the index, to make sure approximate search finds
-    the point itself. Reports 2 metrics - "self-recall" and "efficiency".
+@dataclass
+class SearchStats:
+    """
+    Contains statistics for one or more search runs, including the number of
+    internal nodes that were fetched (`visited_members`) and the number
+    of times the distance metric was invoked (`computed_distances`).
 
-    Self-recall is the share of queried vectors, that were succesfully found.
+    Other derivative metrics include the `mean_recall` and `mean_efficiency`.
+    Recall is the share of queried vectors, that were successfully found.
     Efficiency describes the number of distances that had to be computed for
     each query, normalized to size of the `index`. Highest efficiency is 0.(9),
     lowest is zero. Highest is achieved, when the distance metric was computed
     just once per query. Lowest happens during exact search, when every distance
     to every present vector had to be computed.
+    """
+
+    index_size: int
+    count_queries: int
+    count_matches: int
+
+    visited_members: int
+    computed_distances: int
+
+    @property
+    def mean_efficiency(self) -> float:
+        return 1 - float(self.computed_distances) / (
+            self.count_queries * self.index_size
+        )
+
+    @property
+    def mean_recall(self) -> float:
+        return self.count_matches / self.count_queries
+
+
+def self_recall(index: Index, sample: float = 1, **kwargs) -> SearchStats:
+    """Simplest benchmark for a quality of search, which queries every
+    existing member of the index, to make sure approximate search finds
+    the point itself.
 
     :param index: Non-empty pre-constructed index
     :type index: Index
     :param sample: Share of vectors to search, defaults to 1
     :type sample: float
-    :return: Value from 0 to 1, for the share of found self-references
-    :rtype: float, float
+    :return: Evaluation report with key metrics
+    :rtype: SearchStats
     """
     if len(index) == 0:
         return 0
@@ -85,9 +112,14 @@ def recall_members(index: Index, sample: float = 1, **kwargs) -> Tuple[float, fl
 
     queries = index.get_vectors(keys, index.dtype)
     matches: BatchMatches = index.search(queries, **kwargs)
-    recall_first: float = matches.recall_first(keys)
-    efficiency: float = 1 - float(matches.measurements) / (len(keys) * len(index))
-    return recall_first, efficiency
+    count_matches: float = matches.count_matches(keys)
+    return SearchStats(
+        index_size=len(index),
+        count_queries=len(keys),
+        count_matches=count_matches,
+        visited_members=matches.visited_members,
+        computed_distances=matches.computed_distances,
+    )
 
 
 def measure_seconds(f: Callable) -> Tuple[float, Any]:
diff --git a/python/usearch/index.py b/python/usearch/index.py
index 3668c423..4d4799f6 100644
--- a/python/usearch/index.py
+++ b/python/usearch/index.py
@@ -50,13 +50,13 @@ def _normalize_dtype(dtype, metric: MetricKind = MetricKind.IP) -> ScalarKind:
         "f64": ScalarKind.F64,
         "f32": ScalarKind.F32,
         "f16": ScalarKind.F16,
-        "f8": ScalarKind.F8,
+        "i8": ScalarKind.I8,
         "b1": ScalarKind.B1,
         "b1x8": ScalarKind.B1,
         np.float64: ScalarKind.F64,
         np.float32: ScalarKind.F32,
         np.float16: ScalarKind.F16,
-        np.int8: ScalarKind.F8,
+        np.int8: ScalarKind.I8,
     }
     return _normalize[dtype]
 
@@ -66,7 +66,7 @@ def _to_numpy_compatible_dtype(dtype: ScalarKind) -> ScalarKind:
         ScalarKind.F64: ScalarKind.F64,
         ScalarKind.F32: ScalarKind.F32,
         ScalarKind.F16: ScalarKind.F16,
-        ScalarKind.F8: ScalarKind.F16,
+        ScalarKind.I8: ScalarKind.F16,
         ScalarKind.B1: ScalarKind.B1,
     }
     return _normalize[dtype]
@@ -77,7 +77,7 @@ def _to_numpy_dtype(dtype: ScalarKind):
         ScalarKind.F64: np.float64,
         ScalarKind.F32: np.float32,
         ScalarKind.F16: np.float16,
-        ScalarKind.F8: np.float16,
+        ScalarKind.I8: np.float16,
         ScalarKind.B1: np.uint8,
     }
     return _normalize[dtype]
@@ -120,7 +120,9 @@ def _search_in_compiled(
         vectors = vectors.reshape(1, len(vectors))
     count_vectors = vectors.shape[0]
 
-    def distil_batch(batch_matches: BatchMatches) -> Union[BatchMatches, Matches]:
+    def distil_batch(
+        batch_matches: BatchMatches,
+    ) -> Union[BatchMatches, Matches]:
         return batch_matches[0] if count_vectors == 1 else batch_matches
 
     if log and batch_size == 0:
@@ -249,8 +251,8 @@ class Matches:
     keys: np.ndarray
     distances: np.ndarray
 
-    lookups: int
-    measurements: int
+    visited_members: int
+    computed_distances: int
 
     def __len__(self) -> int:
         return len(self.keys)
@@ -284,8 +286,8 @@ class BatchMatches:
     distances: np.ndarray
     counts: np.ndarray
 
-    lookups: int
-    measurements: int
+    visited_members: int
+    computed_distances: int
 
     def __len__(self) -> int:
         return len(self.counts)
@@ -295,8 +297,8 @@ def __getitem__(self, index: int) -> Matches:
             return Matches(
                 keys=self.keys[index, : self.counts[index]],
                 distances=self.distances[index, : self.counts[index]],
-                lookups=self.lookups // len(self),
-                measurements=self.measurements // len(self),
+                visited_members=self.visited_members // len(self),
+                computed_distances=self.computed_distances // len(self),
             )
         else:
             raise IndexError(f"`index` must be an integer under {len(self)}")
@@ -306,19 +308,26 @@ def to_list(self) -> List[List[tuple]]:
         list_of_matches = [self.__getitem__(row) for row in range(self.__len__())]
         return [match.to_list() for matches in list_of_matches for match in matches]
 
-    def recall_first(self, expected: np.ndarray) -> float:
-        """Measures recall [0, 1] as of `Matches` that contain the corresponding
-        `expected` entry as the first result."""
-        return np.sum(self.keys[:, 0] == expected) / len(expected)
-
-    def recall(self, expected: np.ndarray) -> float:
+    def mean_recall(self, expected: np.ndarray, k: Optional[int] = None) -> float:
         """Measures recall [0, 1] as of `Matches` that contain the corresponding
         `expected` entry anywhere among results."""
-        assert len(expected) == self.batch_size
+        return self.count_matches(expected, k=k) / len(expected)
+
+    def count_matches(self, expected: np.ndarray, k: Optional[int] = None) -> int:
+        """Measures recall [0, len(expected)] as of `Matches` that contain the corresponding
+        `expected` entry anywhere among results.
+        """
+        assert len(expected) == len(self)
         recall = 0
-        for i in range(self.batch_size):
-            recall += expected[i] in self.keys[i]
-        return recall / len(expected)
+        if k is None:
+            k = self.keys.shape[1]
+
+        if k == 1:
+            recall = np.sum(self.keys[:, 0] == expected)
+        else:
+            for i in range(len(self)):
+                recall += expected[i] in self.keys[i, :k]
+        return recall
 
     def __repr__(self) -> str:
         return f"usearch.BatchMatches({np.sum(self.counts)} across {len(self)} queries)"
@@ -369,7 +378,7 @@ def __init__(
 
         :param dtype: Scalar type for internal vector storage, defaults to None
         :type dtype: Optional[Union[str, ScalarKind]], optional
-            For continuous metrics can be: f16, f32, f64, or f8.
+            For continuous metrics can be: f16, f32, f64, or i8.
             For bitwise metrics it's implementation-defined, and can't change.
             Example: you can use the `f16` index with `f32` vectors in Euclidean space,
             which will be automatically downcasted.
diff --git a/python/usearch/numba.py b/python/usearch/numba.py
index 91b5e753..5885501d 100644
--- a/python/usearch/numba.py
+++ b/python/usearch/numba.py
@@ -44,7 +44,7 @@ def jit(
     )
 
     numba_supported_types = (
-        ScalarKind.F8,
+        ScalarKind.I8,
         # Half-precision is still unsupported
         # https://github.com/numba/numba/issues/4402
         # ScalarKind.F16: np.float16,
@@ -55,7 +55,7 @@ def jit(
         return metric
 
     scalar_kind_to_accumulator_type = {
-        ScalarKind.F8: types.int32,
+        ScalarKind.I8: types.int32,
         ScalarKind.F16: types.float16,
         ScalarKind.F32: types.float32,
         ScalarKind.F64: types.float64,
@@ -98,7 +98,7 @@ def numba_l2sq(a, b):
         return types.float32(ab_delta_sq)
 
     scalar_kind_to_signature = {
-        ScalarKind.F8: signature_i8args,
+        ScalarKind.I8: signature_i8args,
         ScalarKind.F16: signature_f16args,
         ScalarKind.F32: signature_f32args,
         ScalarKind.F64: signature_f64args,
@@ -110,7 +110,7 @@ def numba_l2sq(a, b):
         MetricKind.L2sq: numba_l2sq,
     }
 
-    if dtype == ScalarKind.F8 and metric == MetricKind.IP:
+    if dtype == ScalarKind.I8 and metric == MetricKind.IP:
         metric = MetricKind.Cos
 
     pointer = cfunc(scalar_kind_to_signature[dtype])(metric_kind_to_function[metric])
diff --git a/python/usearch/server.py b/python/usearch/server.py
index b90e88c2..5f53b90e 100644
--- a/python/usearch/server.py
+++ b/python/usearch/server.py
@@ -13,7 +13,7 @@
 def _ascii_to_vector(string: str) -> np.ndarray:
     """
     WARNING: A dirty performance hack!
-    Assuming the `f8` vectors in our implementations are just integers,
+    Assuming the `i8` vectors in our implementations are just integers,
     and generally contain scalars in the [0, 100] range, we can transmit
     them as JSON-embedded strings. The only symbols we must avoid are
     the double-quote '"' (code 22) and backslash '\' (code 60).
diff --git a/rust/lib.cpp b/rust/lib.cpp
index a467f3e8..1afd67e8 100644
--- a/rust/lib.cpp
+++ b/rust/lib.cpp
@@ -11,7 +11,7 @@ using search_result_t = typename index_t::search_result_t;
 Index::Index(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
 
 void Index::add_in_thread(key_t key, rust::Slice<float const> vector, size_t thread) const {
-    index_add_config_t config;
+    index_update_config_t config;
     config.thread = thread;
     config.expansion = index_->expansion_add();
     index_->add(key, vector.data(), config).error.raise();
@@ -87,7 +87,7 @@ metric_kind_t rust_to_cpp_metric(MetricKind value) {
 
 scalar_kind_t rust_to_cpp_scalar(ScalarKind value) {
     switch (value) {
-    case ScalarKind::F8: return scalar_kind_t::f8_k;
+    case ScalarKind::I8: return scalar_kind_t::i8_k;
     case ScalarKind::F16: return scalar_kind_t::f16_k;
     case ScalarKind::F32: return scalar_kind_t::f32_k;
     case ScalarKind::F64: return scalar_kind_t::f64_k;
diff --git a/rust/lib.rs b/rust/lib.rs
index 76969008..46aeabb7 100644
--- a/rust/lib.rs
+++ b/rust/lib.rs
@@ -23,7 +23,7 @@ pub mod ffi {
         F64,
         F32,
         F16,
-        F8,
+        I8,
         B1,
     }
 


Test patch:
diff --git a/cpp/test.cpp b/cpp/test.cpp
index 1443c973..82075c02 100644
--- a/cpp/test.cpp
+++ b/cpp/test.cpp
@@ -96,7 +96,7 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
     executor_default_t executor;
     index.reserve({vectors.size(), executor.size()});
     executor.execute_bulk(vectors.size() - 3, [&](std::size_t thread, std::size_t task) {
-        index_add_config_t config;
+        index_update_config_t config;
         config.thread = thread;
         index.add(key_max - task - 3, vectors[task + 3].data(), args..., config);
     });
@@ -114,6 +114,9 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
         std::vector<scalar_t> vec_recovered_from_view(dimensions);
         index.get(key_second, vec_recovered_from_view.data());
         expect(std::equal(vector_second, vector_second + dimensions, vec_recovered_from_view.data()));
+
+        auto compaction_result = index.compact();
+        expect(bool(compaction_result));
     }
 
     expect(index.memory_usage() > 0);
@@ -201,7 +204,7 @@ template <typename key_at, typename slot_at> void test_tanimoto(std::size_t dime
 
     index.reserve({batch_size + index.size(), executor.size()});
     executor.execute_bulk(batch_size, [&](std::size_t thread, std::size_t task) {
-        index_add_config_t config;
+        index_update_config_t config;
         config.thread = thread;
         index.add(task + 25000, scalars.data() + index.scalar_words() * task, config);
     });
diff --git a/golang/lib_test.go b/golang/lib_test.go
new file mode 100644
index 00000000..ab2ad332
--- /dev/null
+++ b/golang/lib_test.go
@@ -0,0 +1,105 @@
+package usearch
+
+import (
+	"runtime"
+	"testing"
+)
+
+func TestUSearch(t *testing.T) {
+	runtime.LockOSThread()
+
+	t.Run("Test Index Initialization", func(t *testing.T) {
+		dim := uint(128)
+		conf := DefaultConfig(dim)
+		ind, err := NewIndex(conf)
+		if err != nil {
+			t.Fatalf("Failed to construct the index: %s", err)
+		}
+		defer ind.Destroy()
+
+		found_dims, err := ind.Dimensions()
+		if err != nil {
+			t.Fatalf("Failed to retrieve dimensions: %s", err)
+		}
+		if found_dims != dim {
+			t.Fatalf("Expected %d dimensions, got %d", dim, found_dims)
+		}
+
+		found_len, err := ind.Len()
+		if err != nil {
+			t.Fatalf("Failed to retrieve size: %s", err)
+		}
+		if found_len != 0 {
+			t.Fatalf("Expected size to be 0, got %d", found_len)
+		}
+
+		err = ind.Reserve(100)
+		if err != nil {
+			t.Fatalf("Failed to reserve capacity: %s", err)
+		}
+	})
+
+	t.Run("Test Insertion", func(t *testing.T) {
+		dim := uint(128)
+		conf := DefaultConfig(dim)
+		ind, err := NewIndex(conf)
+		if err != nil {
+			t.Fatalf("Failed to construct the index: %s", err)
+		}
+		defer ind.Destroy()
+
+		err = ind.Reserve(100)
+		if err != nil {
+			t.Fatalf("Failed to reserve capacity: %s", err)
+		}
+
+		vec := make([]float32, dim)
+		vec[0] = 40.0
+		vec[1] = 2.0
+
+		err = ind.Add(42, vec)
+		if err != nil {
+			t.Fatalf("Failed to insert: %s", err)
+		}
+
+		found_len, err := ind.Len()
+		if err != nil {
+			t.Fatalf("Failed to retrieve size after insertion: %s", err)
+		}
+		if found_len != 1 {
+			t.Fatalf("Expected size to be 1, got %d", found_len)
+		}
+	})
+
+	t.Run("Test Search", func(t *testing.T) {
+		dim := uint(128)
+		conf := DefaultConfig(dim)
+		ind, err := NewIndex(conf)
+		if err != nil {
+			t.Fatalf("Failed to construct the index: %s", err)
+		}
+		defer ind.Destroy()
+
+		err = ind.Reserve(100)
+		if err != nil {
+			t.Fatalf("Failed to reserve capacity: %s", err)
+		}
+
+		vec := make([]float32, dim)
+		vec[0] = 40.0
+		vec[1] = 2.0
+
+		err = ind.Add(42, vec)
+		if err != nil {
+			t.Fatalf("Failed to insert: %s", err)
+		}
+
+		keys, distances, err := ind.Search(vec, 10)
+		if err != nil {
+			t.Fatalf("Failed to search: %s", err)
+		}
+		if keys[0] != 42 || distances[0] != 0.0 {
+			t.Fatalf("Expected result 42 with distance 0, got key %d with distance %f", keys[0], distances[0])
+		}
+	})
+}
diff --git a/golang/usearch_par_test.go b/golang/usearch_par_test.go
deleted file mode 100644
index 82da6592..00000000
--- a/golang/usearch_par_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package usearch
-
-import (
-	"sync"
-	"testing"
-)
-
-func TestParallelism(t *testing.T) {
-	totalInserts := 3000
-	ind := NewIndex(DefaultConfig(128))
-	defer ind.Destroy()
-
-	var v0 = make([]float32, 128)
-	v0[0] = 4.4
-	var wg sync.WaitGroup
-	ind.Reserve(totalInserts)
-
-	// second thread
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-		var v1 = make([]float32, 128)
-		for l := 0; l < totalInserts/2; l++ {
-			ind.Add(10000+l, v1)
-		}
-	}()
-
-	for l := 0; l < totalInserts/2; l++ {
-		ind.Add(l, v0)
-	}
-	wg.Wait()
-	if ind.Len() != totalInserts {
-		t.Errorf("wrong length: expected %d, got %d", totalInserts, ind.Len())
-	}
-
-	res := ind.Search(v0[:], 10)
-	if len(res) != 10 {
-		t.Errorf("wrong search result length: expected %d, got %d", 10, len(res))
-	}
-}
diff --git a/golang/usearch_test.go b/golang/usearch_test.go
deleted file mode 100644
index 4f2b84a4..00000000
--- a/golang/usearch_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package usearch
-
-import (
-	"runtime"
-	"testing"
-)
-
-func Assure(err error) {
-	if err != nil {
-		panic(err)
-	}
-}
-func TestUsearch(t *testing.T) {
-	runtime.LockOSThread()
-	dim := 128
-	conf := DefaultConfig(dim)
-	ind := NewIndex(conf)
-	defer ind.Destroy()
-	if ind.VecDimension() != dim {
-		t.Fatalf("expected dimension %d, got %d", dim, ind.VecDimension())
-	}
-
-	if ind.Connectivity() != conf.Connectivity {
-		t.Fatalf("expected connectivity %d, got %d", conf.Connectivity, ind.Connectivity())
-	}
-
-	if ind.Capacity() != conf.InitCapacity {
-		t.Fatalf("expected initial capacity %d, got %d", conf.InitCapacity, ind.Capacity())
-	}
-
-	if ind.Len() != 0 {
-		t.Fatalf("expected empty initial index, got %d", ind.Len())
-	}
-
-	vec := make([]float32, dim)
-	vec[0] = 40.0
-	vec[1] = 2.0
-	Assure(ind.Add(42, vec))
-	if ind.Len() != 1 {
-		t.Fatalf("expected index size 1, got %d", ind.Len())
-	}
-
-	res := ind.Search(vec, 10)
-	if len(res) != 1 {
-		t.Fatalf("expected 1 result, got %d", len(res))
-	}
-
-	if res[0] != 42 {
-		t.Fatalf("expected result 42, got %d", res[0])
-	}
-
-	vec_far := make([]float32, dim)
-	Assure(ind.Add(43, vec_far))
-	vec_close := make([]float32, dim)
-	vec_close[0] = 40.0
-	vec_close[1] = 4.0
-	res = ind.Search(vec_close, 1)
-	if len(res) != 1 {
-		t.Fatalf("expected 1 result, got %d", len(res))
-	}
-	if res[0] != 42 {
-		t.Fatalf("expected closest vector key to be 42, got %d", res[0])
-	}
-
-	res = ind.Search(vec_close, 2)
-	if len(res) != 2 {
-		t.Fatalf("expected 2 results, got %d", len(res))
-	}
-	if !(res[0] == 42 && res[1] == 43) {
-		t.Fatalf("expected closest vector keys to be 42, 43, got %d, %d", res[0], res[1])
-	}
-
-	Assure(ind.Reserve(102))
-	for i := 0; i < 100; i++ {
-		vec[i] += 10
-		Assure(ind.Add(i, vec))
-	}
-	res = ind.Search(vec, 100)
-	if len(res) != 100 {
-		t.Fatalf("expected 100 results, got %d", len(res))
-	}
-}
diff --git a/javascript/test.js b/javascript/test.js
index cff49ad2..a6332ca8 100644
--- a/javascript/test.js
+++ b/javascript/test.js
@@ -1,30 +1,31 @@
 var assert = require('assert');
 var usearch = require('bindings')('usearch');
 
-var index = new usearch.Index({ metric: 'l2sq', connectivity: 16, dimensions: 2 })
-assert.equal(index.connectivity(), 16)
-assert.equal(index.dimensions(), 2)
-assert.equal(index.size(), 0)
+// Single-entry operations
 
-index.add(15, new Float32Array([10, 20]))
-index.add(16, new Float32Array([10, 25]))
-assert.equal(index.size(), 2)
+var index = new usearch.Index({ metric: 'l2sq', connectivity: 16n, dimensions: 2n });
+assert.equal(index.connectivity(), 16n, 'connectivity should be 16');
+assert.equal(index.dimensions(), 2n, 'dimensions should be 2');
+assert.equal(index.size(), 0n, 'initial size should be 0');
 
-var results = index.search(new Float32Array([13, 14]), 2)
-assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]))
-assert.deepEqual(results.distances, new Float32Array([45, 130]))
+index.add(15n, new Float32Array([10, 20]));
+index.add(16n, new Float32Array([10, 25]));
+assert.equal(index.size(), 2n, 'size after adding elements should be 2');
 
-// Batch
+var results = index.search(new Float32Array([13, 14]), 2n);
+assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
+assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
 
-var index2 = new usearch.Index({ metric: 'l2sq', connectivity: 16, dimensions: 2 })
+// Batch operations
 
-const keys = [15, 16]
-const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])]
-index2.add(keys, vectors)
-assert.equal(index.size(), 2)
+var indexBatch = new usearch.Index({ metric: 'l2sq', connectivity: 16n, dimensions: 2n });
+const keys = [15n, 16n];
+const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])];
+indexBatch.add(keys, vectors);
+assert.equal(indexBatch.size(), 2, 'size after adding batch should be 2');
 
-var results = index.search(new Float32Array([13, 14]), 2)
-assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]))
-assert.deepEqual(results.distances, new Float32Array([45, 130]))
+results = indexBatch.search(new Float32Array([13, 14]), 2n);
+assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
+assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
 
-console.log('JavaScript tests passed!')
\ No newline at end of file
+console.log('JavaScript tests passed!');
diff --git a/python/scripts/test.py b/python/scripts/test.py
index dcac7ea6..d0f7b446 100644
--- a/python/scripts/test.py
+++ b/python/scripts/test.py
@@ -33,7 +33,7 @@
     ScalarKind.F32,
     ScalarKind.F64,
     ScalarKind.F16,
-    ScalarKind.F8,
+    ScalarKind.I8,
 ]
 numpy_types = [np.float32, np.float64, np.float16]
 
@@ -108,21 +108,23 @@ def test_index(
     index.add(42, vector)
 
     assert len(index) == 1, "Size after addition"
-    assert 42 in index, "Presense in the index"
+    assert 42 in index, "Presence in the index"
     assert 42 in index.keys, "Presence among keys"
-    assert 43 not in index, "Presense in the index, false positive"
+    assert 43 not in index, "Presence in the index, false positive"
     assert index[42] is not None, "Vector recovery"
     assert index[43] is None, "Vector recovery, false positive"
     assert len(index[42]) == ndim
     if numpy_type != np.byte:
         assert np.allclose(index[42], vector, atol=0.1)
 
-    matches = index.search(vector, 10)
+    matches: Matches = index.search(vector, 10)
     assert len(matches.keys) == 1, "Number of matches"
-    assert len(matches.keys) == len(matches.distances), "Symmetric match subarrays"
+    assert len(matches.keys) == len(matches.distances), "Symmetric match sub-arrays"
     assert len({match.key for match in matches}) == 1, "Iteration over matches"
     assert matches[0].key == 42
     assert matches[0].distance == pytest.approx(0, abs=1e-3)
+    assert matches.computed_distances != 0
+    assert matches.visited_members != 0
 
     # Validating the index structure and metadata:
     assert index.max_level >= 0
@@ -137,7 +139,7 @@ def test_index(
     index.remove(43)
     assert len(index) == 1
 
-    # Try insreting back
+    # Try inserting back
     index.add(43, other_vector)
     assert len(index) == 2
     index.remove(43)
@@ -183,7 +185,7 @@ def test_index(
     index = Index.restore(temporary_usearch_filename)
     assert index is None
 
-    # Try openning a corrupt file
+    # Try opening a corrupt file
     with open(temporary_usearch_filename, "w") as file:
         file.write("Some random string")
     meta = Index.metadata(temporary_usearch_filename)
@@ -280,15 +282,20 @@ def test_exact_recall(
         matches: Matches = index.search(vectors[i], 10, exact=True)
         found_labels = matches.keys
         assert found_labels[0] == i
+        assert matches.computed_distances == len(index)
+        assert matches.visited_members == 0, "Exact search won't traverse the graph"
 
     # Search the whole batch
     if batch_size > 1:
         matches: BatchMatches = index.search(vectors, 10, exact=True)
+        assert matches.computed_distances == len(index) * len(vectors)
+        assert matches.visited_members == 0, "Exact search won't traverse the graph"
+
         found_labels = matches.keys
         for i in range(batch_size):
             assert found_labels[i, 0] == i
 
-    # Match entries aginst themselves
+    # Match entries against themselves
     index_copy: Index = index.copy()
     mapping: dict = index.join(index_copy, exact=True)
     for man, woman in mapping.items():