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

Issue:
Bug: Slow index.add
### Describe the bug

Index creation, vector addition and search loop is 90x slower than identical loop for faiss.

Reproduced in Google colab.

### Steps to reproduce

```
import faiss
from usearch.index import Index

import numpy as np

# Set the seed for reproducibility
np.random.seed(42)

# Generate 500 random vectors of length 1024 with values between 0 and 1
vectors = np.random.rand(500, 1024)
vector=np.random.rand(1024)

%%timeit
# FAISS

indexf = faiss.IndexFlatL2(vectors.shape[-1])
indexf.add(np.array(vectors).astype(np.float32))
D, I = indexf.search(np.array([vector]), 50)
# 1.14 ms ± 4.34 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


%%timeit

index = Index(
    ndim=vectors.shape[-1], # Define the number of dimensions in input vectors
    metric='l2_sq', # Choose 'cos', 'l2sq', 'haversine' or other metric, default = 'ip'
  
   )
index.add(labels= np.arange(len(vectors)), vectors=vectors)
matches, distances, count = index.search(vector, 50, exact=True)
  
# 94.7 ms ± 20.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
```


### Expected behavior

n/a

### USearch version

latest pip

### Operating System

ubuntu

### Hardware architecture

x86

### Which interface are you using?

Python 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/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..98d8f529
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,323 @@
+# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
+###############################
+# Core EditorConfig Options   #
+###############################
+root = true
+# Code files for C# and 
+[*.{cs,csx}]
+indent_style = space
+end_of_line = lf
+tab_width = 4
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8-utf-8-bom
+
+###############################
+# .NET Coding Conventions     #
+###############################
+[*.{cs}]
+# Organize usings
+dotnet_sort_system_directives_first = true
+# this. preferences
+dotnet_style_qualification_for_field = true:error
+dotnet_style_qualification_for_property = true:error
+dotnet_style_qualification_for_method = true:error
+dotnet_style_qualification_for_event = true:error
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:error
+dotnet_style_readonly_field = true:suggestion
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:silent
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+# Code quality rules
+dotnet_code_quality_unused_parameters = all:suggestion
+
+[*.cs]
+
+# TODO: enable this but stop "dotnet format" from applying incorrect fixes and introducing a lot of unwanted changes.
+dotnet_analyzer_diagnostic.severity = none
+
+# Note: these settings cause "dotnet format" to fix the code. You should review each change if you uses "dotnet format".
+dotnet_diagnostic.RCS1036.severity = warning # Remove unnecessary blank line.
+dotnet_diagnostic.RCS1037.severity = warning # Remove trailing white-space.
+dotnet_diagnostic.RCS1097.severity = warning # Remove redundant 'ToString' call.
+dotnet_diagnostic.RCS1138.severity = warning # Add summary to documentation comment.
+dotnet_diagnostic.RCS1139.severity = warning # Add summary element to documentation comment.
+dotnet_diagnostic.RCS1168.severity = warning # Parameter name 'foo' differs from base name 'bar'.
+dotnet_diagnostic.RCS1175.severity = warning # Unused 'this' parameter 'operation'.
+dotnet_diagnostic.RCS1192.severity = warning # Unnecessary usage of verbatim string literal.
+dotnet_diagnostic.RCS1194.severity = warning # Implement exception constructors.
+dotnet_diagnostic.RCS1211.severity = warning # Remove unnecessary else clause.
+dotnet_diagnostic.RCS1214.severity = warning # Unnecessary interpolated string.
+dotnet_diagnostic.RCS1225.severity = warning # Make class sealed.
+dotnet_diagnostic.RCS1232.severity = warning # Order elements in documentation comment.
+
+# Commented out because `dotnet format` change can be disruptive.
+# dotnet_diagnostic.RCS1085.severity = warning # Use auto-implemented property.
+
+# Commented out because `dotnet format` removes the xmldoc element, while we should add the missing documentation instead.
+# dotnet_diagnostic.RCS1228.severity = warning # Unused element in documentation comment.
+
+# Diagnostics elevated as warnings
+dotnet_diagnostic.CA1000.severity = warning # Do not declare static members on generic types
+dotnet_diagnostic.CA1031.severity = warning # Do not catch general exception types
+dotnet_diagnostic.CA1050.severity = warning # Declare types in namespaces
+dotnet_diagnostic.CA1063.severity = warning # Implement IDisposable correctly
+dotnet_diagnostic.CA1064.severity = warning # Exceptions should be public
+dotnet_diagnostic.CA1416.severity = warning # Validate platform compatibility
+dotnet_diagnostic.CA1508.severity = warning # Avoid dead conditional code
+dotnet_diagnostic.CA1852.severity = warning # Sealed classes
+dotnet_diagnostic.CA1859.severity = warning # Use concrete types when possible for improved performance
+dotnet_diagnostic.CA1860.severity = warning # Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance
+dotnet_diagnostic.CA2000.severity = warning # Call System.IDisposable.Dispose on object before all references to it are out of scope
+dotnet_diagnostic.CA2201.severity = warning # Exception type System.Exception is not sufficiently specific
+
+dotnet_diagnostic.IDE0001.severity = warning # Simplify name
+dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary using directives
+dotnet_diagnostic.IDE0009.severity = warning # Add this or Me qualification
+dotnet_diagnostic.IDE0011.severity = warning # Add braces
+dotnet_diagnostic.IDE0018.severity = warning # Inline variable declaration
+dotnet_diagnostic.IDE0032.severity = warning # Use auto-implemented property
+dotnet_diagnostic.IDE0034.severity = warning # Simplify 'default' expression
+dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code
+dotnet_diagnostic.IDE0040.severity = warning # Add accessibility modifiers
+dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references
+dotnet_diagnostic.IDE0050.severity = warning # Convert anonymous type to tuple
+dotnet_diagnostic.IDE0051.severity = warning # Remove unused private member
+dotnet_diagnostic.IDE0055.severity = warning # Formatting rule
+dotnet_diagnostic.IDE0060.severity = warning # Remove unused parameter
+dotnet_diagnostic.IDE0070.severity = warning # Use 'System.HashCode.Combine'
+dotnet_diagnostic.IDE0071.severity = warning # Simplify interpolation
+dotnet_diagnostic.IDE0073.severity = warning # Require file header
+dotnet_diagnostic.IDE0082.severity = warning # Convert typeof to nameof
+dotnet_diagnostic.IDE0090.severity = warning # Simplify new expression
+dotnet_diagnostic.IDE0130.severity = warning # Namespace does not match folder structure
+dotnet_diagnostic.IDE0161.severity = warning # Use file-scoped namespace
+
+# Suppressed diagnostics
+dotnet_diagnostic.CA1002.severity = none # Change 'List<string>' in '...' to use 'Collection<T>' ...
+dotnet_diagnostic.CA1032.severity = none # We're using RCS1194 which seems to cover more ctors
+dotnet_diagnostic.CA1034.severity = none # Do not nest type. Alternatively, change its accessibility so that it is not externally visible
+dotnet_diagnostic.CA1062.severity = none # Disable null check, C# already does it for us
+dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters
+dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
+dotnet_diagnostic.CA1805.severity = none # Member is explicitly initialized to its default value
+dotnet_diagnostic.CA1822.severity = none # Member does not access instance data and can be marked as static
+dotnet_diagnostic.CA1848.severity = none # For improved performance, use the LoggerMessage delegates
+dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
+dotnet_diagnostic.CA2225.severity = none # Operator overloads have named alternates
+dotnet_diagnostic.CA2227.severity = none # Change to be read-only by removing the property setter
+dotnet_diagnostic.CA2253.severity = none # Named placeholders in the logging message template should not be comprised of only numeric characters
+
+dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave
+dotnet_diagnostic.xUnit1004.severity = none # Test methods should not be skipped. Remove the Skip property to start running the test again.
+
+dotnet_diagnostic.RCS1021.severity = none # Use expression-bodied lambda.
+dotnet_diagnostic.RCS1032.severity = none # Remove redundant parentheses.
+dotnet_diagnostic.RCS1061.severity = none # Merge 'if' with nested 'if'.
+dotnet_diagnostic.RCS1069.severity = none # Remove unnecessary case label.
+dotnet_diagnostic.RCS1074.severity = none # Remove redundant constructor.
+dotnet_diagnostic.RCS1077.severity = none # Optimize LINQ method call.
+dotnet_diagnostic.RCS1118.severity = none # Mark local variable as const.
+dotnet_diagnostic.RCS1124.severity = none # Inline local variable.
+dotnet_diagnostic.RCS1129.severity = none # Remove redundant field initialization.
+dotnet_diagnostic.RCS1140.severity = none # Add exception to documentation comment.
+dotnet_diagnostic.RCS1141.severity = none # Add 'param' element to documentation comment.
+dotnet_diagnostic.RCS1142.severity = none # Add 'typeparam' element to documentation comment.
+dotnet_diagnostic.RCS1146.severity = none # Use conditional access.
+dotnet_diagnostic.RCS1151.severity = none # Remove redundant cast.
+dotnet_diagnostic.RCS1158.severity = none # Static member in generic type should use a type parameter.
+dotnet_diagnostic.RCS1161.severity = none # Enum should declare explicit value
+dotnet_diagnostic.RCS1163.severity = none # Unused parameter 'foo'.
+dotnet_diagnostic.RCS1170.severity = none # Use read-only auto-implemented property.
+dotnet_diagnostic.RCS1173.severity = none # Use coalesce expression instead of 'if'.
+dotnet_diagnostic.RCS1181.severity = none # Convert comment to documentation comment.
+dotnet_diagnostic.RCS1186.severity = none # Use Regex instance instead of static method.
+dotnet_diagnostic.RCS1188.severity = none # Remove redundant auto-property initialization.
+dotnet_diagnostic.RCS1189.severity = none # Add region name to #endregion.
+dotnet_diagnostic.RCS1197.severity = none # Optimize StringBuilder.AppendLine call.
+dotnet_diagnostic.RCS1201.severity = none # Use method chaining.
+dotnet_diagnostic.RCS1205.severity = none # Order named arguments according to the order of parameters.
+dotnet_diagnostic.RCS1212.severity = none # Remove redundant assignment.
+dotnet_diagnostic.RCS1217.severity = none # Convert interpolated string to concatenation.
+dotnet_diagnostic.RCS1222.severity = none # Merge preprocessor directives.
+dotnet_diagnostic.RCS1226.severity = none # Add paragraph to documentation comment.
+dotnet_diagnostic.RCS1229.severity = none # Use async/await when necessary.
+dotnet_diagnostic.RCS1234.severity = none # Enum duplicate value
+dotnet_diagnostic.RCS1238.severity = none # Avoid nested ?: operators.
+dotnet_diagnostic.RCS1241.severity = none # Implement IComparable when implementing IComparable<T><T>.
+
+dotnet_diagnostic.IDE0001.severity = none # Simplify name
+dotnet_diagnostic.IDE0002.severity = none # Simplify member access
+dotnet_diagnostic.IDE0004.severity = none # Remove unnecessary cast
+dotnet_diagnostic.IDE0035.severity = none # Remove unreachable code
+dotnet_diagnostic.IDE0051.severity = none # Remove unused private member
+dotnet_diagnostic.IDE0052.severity = none # Remove unread private member
+dotnet_diagnostic.IDE0058.severity = none # Remove unused expression value
+dotnet_diagnostic.IDE0059.severity = none # Unnecessary assignment of a value
+dotnet_diagnostic.IDE0060.severity = none # Remove unused parameter
+dotnet_diagnostic.IDE0080.severity = none # Remove unnecessary suppression operator
+dotnet_diagnostic.IDE0100.severity = none # Remove unnecessary equality operator
+dotnet_diagnostic.IDE0110.severity = none # Remove unnecessary discards
+dotnet_diagnostic.IDE0032.severity = none # Use auto property
+dotnet_diagnostic.IDE0160.severity = none # Use block-scoped namespace
+
+###############################
+# Naming Conventions          #
+###############################
+
+# Styles
+
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+dotnet_naming_style.camel_case_style.capitalization = camel_case
+
+dotnet_naming_style.static_underscored.capitalization = camel_case
+dotnet_naming_style.static_underscored.required_prefix = s_
+
+dotnet_naming_style.underscored.capitalization = camel_case
+dotnet_naming_style.underscored.required_prefix = _
+
+dotnet_naming_style.uppercase_with_underscore_separator.capitalization = all_upper
+dotnet_naming_style.uppercase_with_underscore_separator.word_separator = _
+
+dotnet_naming_style.end_in_async.required_prefix =
+dotnet_naming_style.end_in_async.required_suffix = Async
+dotnet_naming_style.end_in_async.capitalization = pascal_case
+dotnet_naming_style.end_in_async.word_separator =
+
+# Symbols
+
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.applicable_accessibilities  = *
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.local_constant.applicable_kinds = local
+dotnet_naming_symbols.local_constant.applicable_accessibilities  = *
+dotnet_naming_symbols.local_constant.required_modifiers = const
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private
+
+dotnet_naming_symbols.any_async_methods.applicable_kinds = method
+dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
+dotnet_naming_symbols.any_async_methods.required_modifiers = async
+
+# Rules
+
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = error
+
+dotnet_naming_rule.local_constant_should_be_pascal_case.symbols = local_constant
+dotnet_naming_rule.local_constant_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_rule.local_constant_should_be_pascal_case.severity = error
+
+dotnet_naming_rule.private_static_fields_underscored.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_underscored.style = static_underscored
+dotnet_naming_rule.private_static_fields_underscored.severity = error
+
+dotnet_naming_rule.private_fields_underscored.symbols = private_fields
+dotnet_naming_rule.private_fields_underscored.style = underscored
+dotnet_naming_rule.private_fields_underscored.severity = error
+
+dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
+dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
+dotnet_naming_rule.async_methods_end_in_async.severity = error
+
+###############################
+# C# Coding Conventions       #
+###############################
+
+# var preferences
+csharp_style_var_for_built_in_types = false:none
+csharp_style_var_when_type_is_apparent = false:none
+csharp_style_var_elsewhere = false:none
+# Expression-bodied members
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+# Pattern matching preferences
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+# Null-checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+# Expression-level preferences
+csharp_prefer_braces = true:error
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:error
+csharp_style_inlined_variable_declaration = true:suggestion
+
+###############################
+# C# Formatting Rules         #
+###############################
+
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = false # Does not work with resharper, forcing code to be on long lines instead of wrapping
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+# Indentation preferences
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_switch_labels = true
+csharp_indent_labels = flush_left
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+# Wrapping preferences
+csharp_preserve_single_line_statements = true
+csharp_preserve_single_line_blocks = true
+csharp_using_directive_placement = outside_namespace:warning
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = file_scoped:warning
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index df06b94b..fe8fcfe1 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -122,6 +122,22 @@ jobs:
           toolchain: stable
           override: true
 
+  test_golang:
+    name: Test GoLang
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+    - run: git submodule update --init --recursive
+    - name: Set up Go
+      uses: actions/setup-go@v4
+      with:
+        go-version: '1.15'
+    - name: Build C library for cGo
+      run: |
+        make -C ./c libusearch_c.so
+        mv ./c/libusearch_c.so ./golang/libusearch_c.so
+        cd golang && ls && go test -v
+
   test_java:
     name: Test Java
     runs-on: ubuntu-latest
@@ -160,7 +176,7 @@ jobs:
         sudo apt install -y nodejs
         git clone https://github.com/emscripten-core/emsdk.git
         ./emsdk/emsdk install latest
-    - name: Build USearch by Emscripten
+    - name: Build USearch using Emscripten
       run: |
         ./emsdk/emsdk activate latest && source ./emsdk/emsdk_env.sh
         emcmake cmake -DUSEARCH_BUILD_BENCHMARK=0 -DUSEARCH_BUILD_WASM=1 -B ./build -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -s TOTAL_MEMORY=64MB" && emmake make -C ./build
diff --git a/.gitignore b/.gitignore
index 651ddeba..0eefec42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,9 @@ __pycache__
 dist/
 wheelhouse/
 
+# Python Environments
+venv/
+
 # JS wrappers
 package-lock.json
 node_modules/
@@ -39,7 +42,7 @@ node_modules/
 Cargo.lock
 target/
 
-# Java binings
+# Java bindings
 .gradle
 *.class
 
@@ -47,6 +50,10 @@ target/
 .build
 .swiftpm
 
+# C# builds
+[Bb]in/
+[Oo]bj/
+
 # Prerequisites
 *.d
 
@@ -82,3 +89,4 @@ target/
 *.exe
 *.out
 *.app
+
diff --git a/.releaserc b/.releaserc
index 70e5443b..4c482403 100644
--- a/.releaserc
+++ b/.releaserc
@@ -11,6 +11,10 @@
             {
                 "preset": "eslint",
                 "releaseRules": [
+                    {
+                        "tag": "Break",
+                        "release": "major"
+                    },
                     {
                         "tag": "Add",
                         "release": "minor"
@@ -35,6 +39,10 @@
             {
                 "preset": "eslint",
                 "releaseRules": [
+                    {
+                        "tag": "Break",
+                        "release": "major"
+                    },
                     {
                         "tag": "Add",
                         "release": "minor"
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 7add48cd..b14cf595 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -157,5 +157,11 @@
         "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"
+    },
+    "dotnet.defaultSolution": "csharp/libusearch-wrapper.sln",
 }
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c4c17e2e..e6db76b5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 # This CMake file is heavily inspired by following `usearch` CMake:
 # https://github.com/nlohmann/json/blob/develop/CMakeLists.txt
 cmake_minimum_required(VERSION 3.1)
-project(usearch VERSION 0.6.0 LANGUAGES CXX)
+project(usearch VERSION 0.6.0 LANGUAGES C CXX)
 
 # Determine if USearch is built as a subproject (using `add_subdirectory`) or if it is the main project
 set(USEARCH_IS_MAIN_PROJECT OFF)
@@ -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_CLIB "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_CLIB})
+    add_subdirectory(c)
+endif()
diff --git a/Cargo.toml b/Cargo.toml
index 561ad644..816fbfd7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "usearch"
-version = "0.22.3"
+version = "1.1.0"
 authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
 description = "Smaller & Faster Single-File Vector Search Engine from Unum"
 edition = "2021"
diff --git a/README.md b/README.md
index 51e17e4d..5102e9bc 100644
--- a/README.md
+++ b/README.md
@@ -20,12 +20,12 @@ Vector Search Engine<br/>
 <p align="center">
 Euclidean • Angular • Jaccard • Hamming • Haversine • User-Defined Metrics
 <br/>
-<a href="https://unum-cloud.github.io/usearch/cpp">C++11</a> •
-<a href="https://unum-cloud.github.io/usearch/python">Python</a> •
+<a href="https://unum-cloud.github.io/usearch/cpp">C++ 11</a> •
+<a href="https://unum-cloud.github.io/usearch/python">Python 3</a> •
 <a href="https://unum-cloud.github.io/usearch/javascript">JavaScript</a> •
 <a href="https://unum-cloud.github.io/usearch/java">Java</a> •
 <a href="https://unum-cloud.github.io/usearch/rust">Rust</a> •
-<a href="https://unum-cloud.github.io/usearch/c">C99</a> •
+<a href="https://unum-cloud.github.io/usearch/c">C 99</a> •
 <a href="https://unum-cloud.github.io/usearch/objective-c">Objective-C</a> •
 <a href="https://unum-cloud.github.io/usearch/swift">Swift</a> •
 <a href="https://unum-cloud.github.io/usearch/golang">GoLang</a> •
@@ -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%** |
@@ -159,7 +159,46 @@ other_view = Index(ndim=..., metric=CompiledMetric(...))
 other_view.view("index.usearch")
 ```
 
-## Joins
+## Exact, Approximate, and Multi-Index Lookups
+
+Approximate search methods, such as HNSW, are predominantly used when an exact brute-force search becomes too resource-intensive.
+This typically occurs when you have millions of entries in a collection.
+For smaller collections, we offer a more direct approach with the `search` method.
+
+```py
+from usearch.index import search, MetricKind, Matches, BatchMatches
+import numpy as np
+
+# Generate 10'000 random vectors with 1024 dimensions
+vectors = np.random.rand(10_000, 1024).astype(np.float32)
+vector = np.random.rand(1024).astype(np.float32)
+
+one_in_many: Matches = search(vectors, vector, 50, MetricKind.L2sq, exact=True)
+many_in_many: BatchMatches = search(vectors, vectors, 50, MetricKind.L2sq, exact=True)
+```
+
+By passing the `exact=True` argument, the system bypasses indexing altogether and performs a brute-force search through the entire dataset using SIMD-optimized similarity metrics from [SimSIMD](https://github.com/ashvardanian/simsimd).
+When compared to FAISS's `IndexFlatL2` in Google Colab, **[USearch may offer up to a 20x performance improvement](https://github.com/unum-cloud/usearch/issues/176#issuecomment-1666650778)**:
+
+- `faiss.IndexFlatL2`: **55.3 ms**.
+- `usearch.index.search`: **2.54 ms**.
+
+For larger workloads targeting billions or even trillions of vectors, parallel multi-index lookups become invaluable.
+These lookups prevent the need to construct a single, massive index, allowing users to query multiple smaller ones instead.
+
+```py
+from usearch.index import Indexes
+
+multi_index = Indexes(
+    indexes: Iterable[usearch.index.Index] = [...],
+    paths: Iterable[os.PathLike] = [...],
+    view: bool = False,
+    threads: int = 0,
+)
+multi_index.search(...)
+```
+
+## Joins, One-to-One, One-to-Many, and Many-to-Many Mappings
 
 One of the big questions these days is how will AI change the world of databases and data management.
 Most databases are still struggling to implement high-quality fuzzy search, and the only kind of joins they know are deterministic.
@@ -189,9 +228,9 @@ Broader functionality is ported per request.
 | :---------------------- | :----: | :------: | :---: | :---: | :--------: | :---: | :----: | :---: |
 | Add, search             |   ✅    |    ✅     |   ✅   |   ✅   |     ✅      |   ✅   |   ✅    |   ✅   |
 | Save, load, view        |   ✅    |    ✅     |   ✅   |   ✅   |     ✅      |   ✅   |   ✅    |   ✅   |
-| Join                    |   ✅    |    ✅     |   ✅   |   ❌   |     ❌      |   ❌   |   ❌    |   ❌   |
 | User-defined metrics    |   ✅    |    ✅     |   ✅   |   ❌   |     ❌      |   ❌   |   ❌    |   ❌   |
-| Variable-length vectors |   ✅    |    ✅     |   ❌   |   ❌   |     ❌      |   ❌   |   ❌    |   ❌   |
+| Joins                    |   ✅    |    ✅     |   ❌   |   ❌   |     ❌      |   ❌   |   ❌    |   ❌   |
+| Variable-length vectors |   ✅    |    ❌     |   ❌   |   ❌   |     ❌      |   ❌   |   ❌    |   ❌   |
 | 4B+ capacities          |   ✅    |    ❌     |   ❌   |   ❌   |     ❌      |   ❌   |   ❌    |   ❌   |
 
 ## Application Examples
@@ -281,9 +320,9 @@ matches = index.search(fingerprints, 10)
 ## Integrations
 
 - [x] GPT-Cache.
-- [ ] LangChain.
-- [ ] Microsoft Semantic Kernel.
+- [x] LangChain.
 - [ ] ClickHouse.
+- [ ] Microsoft Semantic Kernel.
 
 ## Citations
 
diff --git a/VERSION b/VERSION
index d90746a7..9084fa2f 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.22.3
+1.1.0
diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
new file mode 100644
index 00000000..f673ada7
--- /dev/null
+++ b/c/CMakeLists.txt
@@ -0,0 +1,53 @@
+
+
+set(USEARCH_PUNNED_INCLUDE_DIRS
+    "${CMAKE_CURRENT_SOURCE_DIR}/../include"
+    "${CMAKE_CURRENT_SOURCE_DIR}/../fp16/include"
+    "${CMAKE_CURRENT_SOURCE_DIR}/../robin-map/include"
+    "${CMAKE_CURRENT_SOURCE_DIR}/../simsimd/include"
+    "${CMAKE_CURRENT_SOURCE_DIR}/"
+)
+
+# This article discusses a better way to allow building either static or shared libraries
+# https://alexreinking.com/blog/building-a-dual-shared-and-static-library-with-cmake.html
+if (${USEARCH_BUILD_STATIC})
+    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+        set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -static-libstdc++")
+    elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+        set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -static")
+    endif()
+
+    add_library(usearch_c STATIC "${CMAKE_CURRENT_SOURCE_DIR}/lib.cpp")
+else()
+    add_library(usearch_c SHARED "${CMAKE_CURRENT_SOURCE_DIR}/lib.cpp")
+endif()
+
+set_target_properties(usearch_c PROPERTIES POSITION_INDEPENDENT_CODE ON)
+set_target_properties(usearch_c PROPERTIES CXX_STANDARD 11)
+set_target_properties(usearch_c PROPERTIES C_STANDARD 99)
+
+target_include_directories(usearch_c PRIVATE ${USEARCH_PUNNED_INCLUDE_DIRS})
+set_target_properties(usearch_c PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+set_target_properties(usearch_c PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+set_target_properties(usearch_c PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+
+
+target_include_directories(usearch_c PRIVATE ${USEARCH_PUNNED_INCLUDE_DIRS})
+
+if(${USEARCH_DEBUG_BUILD_ASAN})
+    # For ASAN in debug builds
+    target_link_libraries(usearch_c PRIVATE gcov)
+endif()
+
+if(${USEARCH_USE_SIMSIMD})
+    target_compile_definitions(usearch_c PRIVATE USEARCH_USE_SIMSIMD=1)
+endif()
+if (${USEARCH_LOOKUP_LABEL})
+    target_compile_definitions(usearch_c PRIVATE USEARCH_LOOKUP_LABEL=1)
+endif()
+
+if(${USEARCH_BUILD_TEST})
+    add_executable(test_c "${CMAKE_CURRENT_SOURCE_DIR}/test.c")
+    target_link_libraries(test_c usearch_c)
+    set_target_properties(test_c PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+endif()
diff --git a/c/Makefile b/c/Makefile
index b13bfe5d..d1d5413d 100644
--- a/c/Makefile
+++ b/c/Makefile
@@ -1,17 +1,29 @@
-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 -pedantic
 
 HEADER_INCLUDES = -I.  -I ../include/  -I ../fp16/include/ -I ../robin-map/include/
 
-.PHONY: build
-build:
-	$(CXX) $(CXX_FLAGS) -o libusearch.so -O3 lib.cpp $(HEADER_INCLUDES) -shared -fPIC
+.PHONY: libusearch_c.a
+libusearch_c.a:
+	$(CXX) $(CXX_FLAGS) -o libusearch_c.a -O3 lib.cpp $(HEADER_INCLUDES) -static
 
-.PHONY: test
-test:
-	$(CC) $(C_FLAGS) test.c -L. -lusearch -Wl,-rpath,. -o test
+.PHONY: libusearch_c.so
+libusearch_c.so:
+	$(CXX) $(CXX_FLAGS) -o libusearch_c.so -O3 lib.cpp $(HEADER_INCLUDES) -shared -fPIC
+
+.PHONY: libusearch_c.dll
+libusearch_c.dll:
+	$(CXX) $(CXX_FLAGS) -o libusearch_c.dll -O3 lib.cpp $(HEADER_INCLUDES) -shared -fPIC
+
+.PHONY: libusearch_c.dylib
+libusearch_c.dylib:
+	$(CXX) $(CXX_FLAGS) -o libusearch_c.dylib -O3 lib.cpp $(HEADER_INCLUDES) -dynamiclib -fPIC
+
+.PHONY: test_c
+test_c:
+	$(CC) $(C_FLAGS) test.c -L. -lusearch_c -Wl,-rpath,. -o test
diff --git a/c/README.md b/c/README.md
new file mode 100644
index 00000000..6eadeb4e
--- /dev/null
+++ b/c/README.md
@@ -0,0 +1,34 @@
+# USearch for C
+
+## Installation
+
+The simplest form to integrate is to copy the contents of `usearch/c/` into your project.
+To build the library `libusearch_c`, pass enable the `USEARCH_BUILD_CLIB` CMake option:
+
+```bash
+ cmake -DUSEARCH_BUILD_CLIB=1 -DUSEARCH_BUILD_STATIC=0 -DUSEARCH_BUILD_TEST=0 -DUSEARCH_BUILD_BENCHMARK=0 ..
+ ```
+
+You can choose between a static and dynamic library by setting `USEARCH_BUILD_STATIC`.
+
+## Quickstart
+
+```c
+usearch_error_t error = NULL;
+usearch_init_options_t opts = create_options(dimensions);
+usearch_index_t index = usearch_init(&opts, &error);
+usearch_reserve(index, vectors_count, &error);
+
+float vector[dimensions] = {...};
+usearch_add(index, 42, &vector[0], usearch_scalar_f32_k, &error);
+
+ASSERT(!error, error);
+ASSERT(usearch_size(index, &error) == vectors_count, error);
+ASSERT(usearch_capacity(index, &error) == vectors_count, error);
+
+usearch_key_t found_keys[10];
+float found_distances[10];
+size_t found_count = usearch_search(index, &vector[0], usearch_scalar_f32_k, 10, &found_keys[0], &found_distances[0], &error);
+
+usearch_free(index, &error);
+```
diff --git a/c/lib.cpp b/c/lib.cpp
index 7ea26c91..e07cbb1f 100644
--- a/c/lib.cpp
+++ b/c/lib.cpp
@@ -2,10 +2,6 @@
 
 #include <usearch/index_dense.hpp>
 
-#ifndef USEARCH_EXPORT
-#define USEARCH_EXPORT
-#endif
-
 extern "C" {
 #include "usearch.h"
 }
@@ -41,7 +37,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 +48,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 +59,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 +70,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..2a17c06c 100644
--- a/c/usearch.h
+++ b/c/usearch.h
@@ -5,38 +5,55 @@
 extern "C" {
 #endif
 #ifndef USEARCH_EXPORT
+#if defined(_WIN32) && !defined(__MINGW32__)
+#define USEARCH_EXPORT __declspec(dllexport)
+#else
 #define USEARCH_EXPORT
 #endif
+#endif
 #include <stdbool.h> // `bool`
 #include <stdint.h>  // `size_t`
 
 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 +105,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 +131,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 +149,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 +183,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/conanfile.py b/conanfile.py
index 2636a91c..07c31ffb 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -4,7 +4,7 @@
 class USearchConan(ConanFile):
 
     name = 'USearch'
-    version = '0.22.3'
+    version = '1.1.0'
     license = 'Apache 2.0'
     url = 'https://github.com/unum-cloud/usearch'
     description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index e73a45ef..7844e295 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -19,18 +19,24 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
     set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wno-conversion -Wno-unknown-pragmas")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=1")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
 
+    if(${USEARCH_USE_OPENMP})
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
+    endif()
+
 elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
     set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
     set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
     set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -g")
     set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
     set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wfatal-errors")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
 
+    if(${USEARCH_USE_OPENMP})
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
+    endif()
+
 elseif(CMAKE_CXX_COMPILER_ID STREQUAL "NVIDIA" OR CMAKE_CXX_COMPILER_ID STREQUAL "NVHPC")
     set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
     set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --extended-lambda")
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/csharp/DEV_SETUP.md b/csharp/DEV_SETUP.md
new file mode 100644
index 00000000..a9c6f404
--- /dev/null
+++ b/csharp/DEV_SETUP.md
@@ -0,0 +1,14 @@
+# System setup
+To get started, you'll need dotnet. 
+
+https://dotnet.microsoft.com/en-us/download
+
+# Tests
+
+Linux/OSx
+    
+    build_and_test.sh
+
+Windows
+
+    build_and_test.cmd
\ No newline at end of file
diff --git a/csharp/libusearch-wrapper.sln b/csharp/libusearch-wrapper.sln
new file mode 100644
index 00000000..f70438b3
--- /dev/null
+++ b/csharp/libusearch-wrapper.sln
@@ -0,0 +1,34 @@
+﻿
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3A93FBE3-99F5-4179-88AB-E8A887C4E85C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibUSearch", "src\LibUSearch\LibUSearch.csproj", "{8828F7DE-3AB9-4CAE-BFFA-3EA9355BD976}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibUSearch.Tests", "src\LibUSearch.Tests\LibUSearch.Tests.csproj", "{F7F5E1D6-25DD-4FE8-ABE9-B05E7449B2A9}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{8828F7DE-3AB9-4CAE-BFFA-3EA9355BD976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8828F7DE-3AB9-4CAE-BFFA-3EA9355BD976}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8828F7DE-3AB9-4CAE-BFFA-3EA9355BD976}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8828F7DE-3AB9-4CAE-BFFA-3EA9355BD976}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F7F5E1D6-25DD-4FE8-ABE9-B05E7449B2A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F7F5E1D6-25DD-4FE8-ABE9-B05E7449B2A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F7F5E1D6-25DD-4FE8-ABE9-B05E7449B2A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F7F5E1D6-25DD-4FE8-ABE9-B05E7449B2A9}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{8828F7DE-3AB9-4CAE-BFFA-3EA9355BD976} = {3A93FBE3-99F5-4179-88AB-E8A887C4E85C}
+		{F7F5E1D6-25DD-4FE8-ABE9-B05E7449B2A9} = {3A93FBE3-99F5-4179-88AB-E8A887C4E85C}
+	EndGlobalSection
+EndGlobal
diff --git a/csharp/src/LibUSearch.Tests/InitTests.cs b/csharp/src/LibUSearch.Tests/InitTests.cs
new file mode 100644
index 00000000..d968b41a
--- /dev/null
+++ b/csharp/src/LibUSearch.Tests/InitTests.cs
@@ -0,0 +1,211 @@
+namespace LibUsearch.Tests;
+
+public class InitTests
+{
+    // Epsilon for real values comparings
+    private const float Epsilon = 0.000001f;
+
+    [Fact]
+    public void InitializedAndDisposedSucceeds()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
+        {
+            Assert.NotNull(index);
+            Assert.Equal((uint)3, index.Dimensions());
+        }
+    }
+
+    [Fact]
+    public void AddDoubleVectorSucceeds()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float64, 3))
+        {
+            index.Add(1, new double[] { 1.0, 2.0, 3.0 });
+            Assert.True(index.Contains(1));
+            Assert.False(index.Contains(2));
+            Assert.Equal((uint)1, index.Size());
+        }
+    }
+
+    [Fact]
+    public void AddFloatVectorSucceeds()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
+        {
+            index.Add(1, new float[] { 1.0f, 2.0f, 3.0f });
+            Assert.Equal((uint)1, index.Size());
+            Assert.True(index.Contains(1));
+            Assert.False(index.Contains(2));
+        }
+    }
+
+    [Fact]
+    public void AddGetFloatVectorSucceeds()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
+        {
+            var inputVector = new float[] { 1.0f, 2.0f, 3.0f };
+            index.Add(1, inputVector);
+            Assert.Equal((uint)1, index.Size());
+            Assert.True(index.Contains(1));
+            Assert.False(index.Contains(2));
+            Assert.True(index.Get(1, out float[] retrievedVector));
+            Assert.NotNull(retrievedVector);
+            bool areEqual = inputVector
+                                    .Zip(retrievedVector, (a, b) => (a, b))
+                                    .All(pair => Math.Abs(pair.a - pair.b) <= Epsilon);
+
+            Assert.True(areEqual);
+        }
+    }
+
+
+    [Fact]
+    public void AddGetDoubleVectorSucceeds()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
+        {
+            var inputVector = new double[] { 1.0, 2.0, 3.33 };
+            index.Add(1, inputVector);
+            Assert.Equal((uint)1, index.Size());
+            Assert.True(index.Contains(1));
+            Assert.False(index.Contains(2));
+            Assert.True(index.Get(1, out double[] retrievedVector));
+            Assert.NotNull(retrievedVector);
+            bool areEqual = inputVector
+                                    .Zip(retrievedVector, (a, b) => (a, b))
+                                    .All(pair => Math.Abs(pair.a - pair.b) <= Epsilon);
+
+            Assert.True(areEqual);
+        }
+    }
+
+
+    // TODO return back this test when std::runtime_error wont be thrown from USearch lib
+    [Fact(Skip = "This test is temporarily disabled because of hanging.")]
+    public void ItThrowsException_AddAddWithSameKeyAndGetVector()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
+        {
+            ulong key = 1;
+            float[] vector1 = { 1.0f, 2.0f, 33.3f };
+            index.Add(key, vector1);
+            float[] vector2 = { 3.0f, 4.0f, 34.2f };
+            Assert.Throws<USearchIndex.USearchException>(() => index.Add(key, vector2));
+        }
+    }
+
+    [Fact]
+    public void TestAddRemoveAddAndGet()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
+        {
+            ulong key = 1;
+            float[] vector = { 1.0f, 2.0f };
+
+            index.Add(key, vector);
+            Assert.True(index.Contains(key));
+            index.Remove(key);
+            Assert.False(index.Contains(key));
+            index.Add(key, vector);
+            Assert.True(index.Contains(key));
+            Assert.True(index.Get(key, out float[] retrievedVector));
+            Assert.Equal(vector, retrievedVector);
+        }
+    }
+
+    [Fact]
+    public void TestAddRemoveAddWithSameKeyVectorAndGet()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
+        {
+            ulong key = 1;
+            float[] vector = { 1.0f, 2.0f };
+
+            index.Add(key, vector);
+            Assert.True(index.Contains(key));
+            index.Remove(key);
+            Assert.False(index.Contains(key));
+            index.Add(key, vector);
+            Assert.True(index.Contains(key));
+            Assert.True(index.Get(key, out float[] retrievedVector));
+            Assert.Equal(vector, retrievedVector);
+        }
+    }
+
+    //[Fact(Skip = "This test is temporarily disabled because of hanging.")]
+    [Fact]
+    public void TestGetKeyThatNotExist()
+    {
+        using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
+        {
+            Assert.False(index.Get(1, out float[] retrievedVector));
+            Assert.False(index.Contains(1));
+        }
+    }
+
+    [Fact]
+    public void Search_FindsExpectedResults()
+    {
+        using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
+        {
+            index.AddMany(
+                Enumerable.Range(1, 5).Select(i => (ulong)i).ToArray(),
+                new float[][]
+                {
+                    new float[] { -0.5f, -0.6f },
+                    new float[] { -0.1f, -0.1f },
+                    new float[] { 0.2f, 0.2f },
+                    new float[] { 0.7f, 0.8f },
+                    new float[] { 0.9f, 1.0f }
+                }
+            );
+            var queryVector = new float[] { 0.0f, 0.0f };
+
+            var matches = index.Search(queryVector, 2, out ulong[] keys, out float[] distances);
+
+            Assert.Equal(2, matches);
+            Assert.Contains(2UL, keys);
+            Assert.Contains(3UL, keys);
+            Assert.Equal(0.02, distances[0], Epsilon);
+            Assert.Equal(0.08, distances[1], Epsilon);
+        }
+    }
+
+    [Fact]
+    public void Search_AfterRemovingKey_FindsExpectedResults()
+    {
+        using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
+        {
+            index.Add(1, new float[] { 0.1f, 0.2f });
+            index.Add(2, new float[] { 0.2f, 0.3f });
+            index.Add(3, new float[] { 0.3f, 0.4f });
+            index.Remove(2);
+            var queryVector = new float[] { 0.2f, 0.3f };
+
+            var matches = index.Search(queryVector, 2, out ulong[] keys, out float[] distances);
+
+            Assert.Equal(2, matches);
+            Assert.Contains(1UL, keys);
+            Assert.Contains(3UL, keys);
+            Assert.DoesNotContain(2UL, keys);
+        }
+    }
+
+    [Fact]
+    public void Search_WithVectorsLessThanResultsLimit()
+    {
+        using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
+        {
+            index.Add(1, new float[] { 0.9f, 0.9f });
+            index.Add(2, new float[] { 0.8f, 0.8f });
+            var queryVector = new float[] { 0.1f, 0.2f };
+
+            var matches = index.Search(queryVector, 10, out ulong[] keys, out float[] distances);
+
+            Assert.Equal(2, matches);
+            Assert.Contains(1UL, keys);
+            Assert.Contains(2UL, keys);
+        }
+    }
+}
diff --git a/csharp/src/LibUSearch.Tests/LibUSearch.Tests.csproj b/csharp/src/LibUSearch.Tests/LibUSearch.Tests.csproj
new file mode 100644
index 00000000..7bbecfea
--- /dev/null
+++ b/csharp/src/LibUSearch.Tests/LibUSearch.Tests.csproj
@@ -0,0 +1,43 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <IsPackable>false</IsPackable>
+    <Nullable>enable</Nullable>
+    
+
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+    <PackageReference Include="xunit" Version="2.4.2" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+      <PrivateAssets>all</PrivateAssets>
+    </PackageReference>
+    <PackageReference Include="coverlet.collector" Version="3.1.2">
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+      <PrivateAssets>all</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\LibUSearch\LibUSearch.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Include="runtimes\*.*">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
+      <Pack>false</Pack>
+    </None>
+  </ItemGroup>
+
+  <!-- Explicitly clear output folders to avoid issues with out-of-date files like dlls-->
+  <Target Name="PostClean" AfterTargets="Clean">
+      <RemoveDir Directories="$(BaseIntermediateOutputPath)" /><!-- obj -->
+      <RemoveDir Directories="$(BaseOutputPath)" /><!-- bin -->
+  </Target>
+
+</Project>
diff --git a/csharp/src/LibUSearch.Tests/Usings.cs b/csharp/src/LibUSearch.Tests/Usings.cs
new file mode 100644
index 00000000..8c927eb7
--- /dev/null
+++ b/csharp/src/LibUSearch.Tests/Usings.cs
@@ -0,0 +1,1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/csharp/src/LibUSearch/LibUSearch.csproj b/csharp/src/LibUSearch/LibUSearch.csproj
new file mode 100644
index 00000000..a2e727cc
--- /dev/null
+++ b/csharp/src/LibUSearch/LibUSearch.csproj
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+  </PropertyGroup>
+
+  <!-- Explicitly clear output folders to avoid issues with out-of-date files like dlls-->
+  <Target Name="PostClean" AfterTargets="Clean">
+      <RemoveDir Directories="$(BaseIntermediateOutputPath)" /><!-- obj -->
+      <RemoveDir Directories="$(BaseOutputPath)" /><!-- bin -->
+  </Target>
+
+</Project>
diff --git a/csharp/src/LibUSearch/NativeMethods.cs b/csharp/src/LibUSearch/NativeMethods.cs
new file mode 100644
index 00000000..04257b58
--- /dev/null
+++ b/csharp/src/LibUSearch/NativeMethods.cs
@@ -0,0 +1,123 @@
+﻿using System.Runtime.InteropServices;
+
+using usearch_index_t = System.IntPtr;
+using usearch_key_t = System.UInt64;
+using usearch_distance_t = System.Single;
+using usearch_error_t = System.IntPtr;
+using size_t = System.UIntPtr;
+using void_ptr_t = System.IntPtr;
+
+internal static class NativeMethods
+{
+    private const string LibraryName = "libusearch_c";
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern usearch_index_t usearch_init(ref IndexOptions options, out usearch_error_t error);
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern void usearch_free(usearch_index_t index, out usearch_error_t error);
+
+    [DllImport(LibraryName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+    public static extern void usearch_save(usearch_index_t index, [MarshalAs(UnmanagedType.LPStr)] string path, out usearch_error_t error);
+
+    [DllImport(LibraryName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+    public static extern void usearch_load(usearch_index_t index, [MarshalAs(UnmanagedType.LPStr)] string path, out usearch_error_t error);
+
+    [DllImport(LibraryName, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+    public static extern void usearch_view(usearch_index_t index, [MarshalAs(UnmanagedType.LPStr)] string path, out usearch_error_t error);
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern size_t usearch_size(usearch_index_t index, out usearch_error_t error);
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern size_t usearch_capacity(usearch_index_t index, out usearch_error_t error);
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern size_t usearch_dimensions(usearch_index_t index, out usearch_error_t error);
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern size_t usearch_connectivity(usearch_index_t index, out usearch_error_t error);
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern void usearch_reserve(usearch_index_t index, size_t capacity, out usearch_error_t error);
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern void usearch_add(
+        usearch_index_t index,
+        usearch_key_t key,
+        [In] float[] vector,
+        ScalarKind vector_kind,
+        out usearch_error_t error
+    );
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern void usearch_add(
+        usearch_index_t index,
+        usearch_key_t key,
+        [In] double[] vector,
+        ScalarKind vector_kind,
+        out usearch_error_t error
+    );
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    [return: MarshalAs(UnmanagedType.I1)]
+    public static extern bool usearch_contains(usearch_index_t index, usearch_key_t key, out usearch_error_t error);
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern size_t usearch_search(
+        usearch_index_t index,
+        void_ptr_t query_vector,
+        ScalarKind query_kind,
+        size_t results_limit,
+        [Out] usearch_key_t[] found_keys,
+        [Out] usearch_distance_t[] found_distances,
+        out usearch_error_t error
+    );
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern size_t usearch_search(
+        usearch_index_t index,
+        [In] float[] query_vector,
+        ScalarKind query_kind,
+        size_t results_limit,
+        [Out] usearch_key_t[] found_keys,
+        [Out] usearch_distance_t[] found_distances,
+        out usearch_error_t error
+    );
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    public static extern size_t usearch_search(
+        usearch_index_t index,
+        [In] double[] query_vector,
+        ScalarKind query_kind,
+        size_t results_limit,
+        [Out] usearch_key_t[] found_keys,
+        [Out] usearch_distance_t[] found_distances,
+        out usearch_error_t error
+    );
+
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    [return: MarshalAs(UnmanagedType.I1)]
+    public static extern bool usearch_get(
+        usearch_index_t index,
+        usearch_key_t key,
+        [Out] float[] vector,
+        ScalarKind vector_kind,
+        out usearch_error_t error
+    );
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    [return: MarshalAs(UnmanagedType.I1)]
+    public static extern bool usearch_get(
+        usearch_index_t index,
+        usearch_key_t key,
+        [Out] double[] vector,
+        ScalarKind vector_kind,
+        out usearch_error_t error
+    );
+
+    [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+    [return: MarshalAs(UnmanagedType.I1)]
+    public static extern bool usearch_remove(usearch_index_t index, usearch_key_t key, out usearch_error_t error);
+}
diff --git a/csharp/src/LibUSearch/USearchIndex.cs b/csharp/src/LibUSearch/USearchIndex.cs
new file mode 100644
index 00000000..fec42e6f
--- /dev/null
+++ b/csharp/src/LibUSearch/USearchIndex.cs
@@ -0,0 +1,262 @@
+using System;
+using System.Runtime.InteropServices;
+using static NativeMethods;
+
+public class USearchIndex : IDisposable
+{
+    private IntPtr _index;
+    private bool _disposedValue = false;
+    private ulong _cachedDimensions;
+
+    public USearchIndex(
+        MetricKind metricKind,
+        ScalarKind quantization,
+        ulong dimensions,
+        ulong connectivity = 0,
+        ulong expansionAdd = 0,
+        ulong expansionSearch = 0
+    // CustomDistanceFunction? customMetric = null
+    )
+    {
+        IndexOptions initOptions = new IndexOptions
+        {
+            metric_kind = metricKind,
+            metric = default,
+            quantization = quantization,
+            dimensions = dimensions,
+            connectivity = connectivity,
+            expansion_add = expansionAdd,
+            expansion_search = expansionSearch
+        };
+
+        this._index = usearch_init(ref initOptions, out IntPtr error);
+        HandleError(error);
+        this._cachedDimensions = dimensions;
+    }
+
+    public USearchIndex(IndexOptions options)
+    {
+        IndexOptions initOptions = options;
+        this._index = usearch_init(ref initOptions, out IntPtr error);
+        HandleError(error);
+        this._cachedDimensions = options.dimensions;
+    }
+
+    public USearchIndex(string path, bool view = false)
+    {
+        IndexOptions initOptions = new IndexOptions();
+        this._index = usearch_init(ref initOptions, out IntPtr error);
+        HandleError(error);
+
+        if (view)
+        {
+            usearch_view(this._index, path, out error);
+        }
+        else
+        {
+            usearch_load(this._index, path, out error);
+        }
+
+        HandleError(error);
+
+        this._cachedDimensions = this.Dimensions();
+    }
+
+    public void Save(string path)
+    {
+        usearch_save(this._index, path, out IntPtr error);
+        HandleError(error);
+    }
+
+    public ulong Size()
+    {
+        ulong size = (ulong)usearch_size(this._index, out IntPtr error);
+        HandleError(error);
+        return size;
+    }
+
+    public ulong Capacity()
+    {
+        ulong capacity = (ulong)usearch_capacity(this._index, out IntPtr error);
+        HandleError(error);
+        return capacity;
+    }
+
+    public ulong Dimensions()
+    {
+        ulong dimensions = (ulong)usearch_dimensions(this._index, out IntPtr error);
+        HandleError(error);
+        return dimensions;
+    }
+
+    public ulong Connectivity()
+    {
+        ulong connectivity = (ulong)usearch_connectivity(this._index, out IntPtr error);
+        HandleError(error);
+        return connectivity;
+    }
+
+    public bool Contains(ulong key)
+    {
+        bool result = usearch_contains(this._index, key, out IntPtr error);
+        HandleError(error);
+        return result;
+    }
+
+    private void IncreaseCapacity(ulong size)
+    {
+        usearch_reserve(this._index, (UIntPtr)(this.Size() + size), out IntPtr error);
+        HandleError(error);
+    }
+
+    private void CheckIncreaseCapacity(ulong size_increase)
+    {
+        ulong size_demand = this.Size() + size_increase;
+        if (this.Capacity() < size_demand)
+        {
+            this.IncreaseCapacity(size_increase);
+        }
+    }
+
+    public void Add(ulong key, float[] vector)
+    {
+        this.CheckIncreaseCapacity(1);
+        usearch_add(this._index, key, vector, ScalarKind.Float32, out IntPtr error);
+        HandleError(error);
+    }
+
+    public void Add(ulong key, double[] vector)
+    {
+        this.CheckIncreaseCapacity(1);
+        usearch_add(this._index, key, vector, ScalarKind.Float64, out IntPtr error);
+        HandleError(error);
+    }
+
+    public void AddMany(ulong[] keys, float[][] vectors)
+    {
+        this.CheckIncreaseCapacity((ulong)vectors.Length);
+        for (int i = 0; i < vectors.Length; i++)
+        {
+            usearch_add(this._index, keys[i], vectors[i], ScalarKind.Float32, out IntPtr error);
+            HandleError(error);
+        }
+    }
+
+    public void AddMany(ulong[] keys, double[][] vectors)
+    {
+        this.CheckIncreaseCapacity((ulong)vectors.Length);
+        for (int i = 0; i < vectors.Length; i++)
+        {
+            usearch_add(this._index, keys[i], vectors[i], ScalarKind.Float64, out IntPtr error);
+            HandleError(error);
+        }
+    }
+
+    public bool Get(ulong key, out float[] vector)
+    {
+        vector = new float[this._cachedDimensions];
+        bool success = usearch_get(this._index, key, vector, ScalarKind.Float32, out IntPtr error);
+        HandleError(error);
+        if (!success)
+        {
+            vector = null;
+        }
+
+        return success;
+    }
+
+    public bool Get(ulong key, out double[] vector)
+    {
+        vector = new double[this._cachedDimensions];
+        bool success = usearch_get(this._index, key, vector, ScalarKind.Float64, out IntPtr error);
+        HandleError(error);
+        if (!success)
+        {
+            vector = null;
+        }
+
+        return success;
+    }
+
+    private int Search<T>(T[] queryVector, int resultsLimit, out ulong[] keys, out float[] distances, ScalarKind scalarKind)
+    {
+        keys = new ulong[resultsLimit];
+        distances = new float[resultsLimit];
+
+        GCHandle handle = GCHandle.Alloc(queryVector, GCHandleType.Pinned);
+        int matches = 0;
+        try
+        {
+            IntPtr queryVectorPtr = handle.AddrOfPinnedObject();
+            matches = checked((int)usearch_search(this._index, queryVectorPtr, scalarKind, (UIntPtr)resultsLimit, keys, distances, out IntPtr error));
+            HandleError(error);
+        }
+        finally
+        {
+            handle.Free();
+        }
+
+        if (matches < resultsLimit)
+        {
+            Array.Resize(ref keys, (int)matches);
+            Array.Resize(ref distances, (int)matches);
+        }
+
+        return matches;
+    }
+
+    public int Search(float[] queryVector, int resultsLimit, out ulong[] keys, out float[] distances)
+    {
+        return this.Search(queryVector, resultsLimit, out keys, out distances, ScalarKind.Float32);
+    }
+
+    public int Search(double[] queryVector, int resultsLimit, out ulong[] keys, out float[] distances)
+    {
+        return this.Search(queryVector, resultsLimit, out keys, out distances, ScalarKind.Float64);
+    }
+
+    public bool Remove(ulong key)
+    {
+        bool success = usearch_remove(this._index, key, out IntPtr error);
+        HandleError(error);
+        return success;
+    }
+
+    private static void HandleError(IntPtr error)
+    {
+        if (error != IntPtr.Zero)
+        {
+            Console.WriteLine($"Error {error}");
+            throw new USearchException($"USearch operation failed: {Marshal.PtrToStringAnsi(error)}");
+        }
+    }
+
+    private void FreeIndex()
+    {
+        if (this._index != IntPtr.Zero)
+        {
+            usearch_free(this._index, out IntPtr error);
+            HandleError(error);
+            this._index = IntPtr.Zero;
+        }
+    }
+
+    public void Dispose()
+    {
+        this.Dispose(true);
+        GC.SuppressFinalize(this);
+    }
+
+    protected virtual void Dispose(bool disposing)
+    {
+        if (!this._disposedValue)
+        {
+            this.FreeIndex();
+            this._disposedValue = true;
+        }
+    }
+
+    ~USearchIndex() => this.Dispose(false);
+
+    public class USearchException : Exception { public USearchException(string message) : base(message) { } }
+}
diff --git a/csharp/src/LibUSearch/USearchTypes.cs b/csharp/src/LibUSearch/USearchTypes.cs
new file mode 100644
index 00000000..9547d77e
--- /dev/null
+++ b/csharp/src/LibUSearch/USearchTypes.cs
@@ -0,0 +1,65 @@
+
+
+using System;
+using System.Runtime.InteropServices;
+
+public enum MetricKind : uint
+{
+    Unknown = 0,
+    Cos,
+    Ip,
+    L2sq,
+    Haversine,
+    Pearson,
+    Jaccard,
+    Hamming,
+    Tanimoto,
+    Sorensen,
+}
+
+public enum ScalarKind : uint
+{
+    Unknown = 0,
+    Float32,
+    Float64,
+    Float16,
+    Int8,
+    Byte1,
+}
+
+// TODO implement custom metric delegate following microsoft guides:
+// 1) https://learn.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
+// 2) https://learn.microsoft.com/en-us/dotnet/framework/interop/marshalling-a-delegate-as-a-callback-method
+// public delegate float usearch_metric_t(IntPtr a, IntPtr b);
+
+[StructLayout(LayoutKind.Sequential)]
+public struct IndexOptions
+{
+    public MetricKind metric_kind;
+    public IntPtr metric;
+    public ScalarKind quantization;
+    public ulong dimensions;
+    public ulong connectivity;
+    public ulong expansion_add;
+    public ulong expansion_search;
+
+    public IndexOptions(
+        MetricKind metric_kind = MetricKind.Unknown,
+        IntPtr metric = default,
+        ScalarKind quantization = ScalarKind.Unknown,
+        ulong dimensions = 0,
+        ulong connectivity = 0,
+        ulong expansion_add = 0,
+        ulong expansion_search = 0
+    )
+    {
+        this.metric_kind = metric_kind;
+        this.metric = metric;
+        this.quantization = quantization;
+        this.dimensions = dimensions;
+        this.connectivity = connectivity;
+        this.expansion_add = expansion_add;
+        this.expansion_search = expansion_search;
+    }
+}
+
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..51d3e24e 100644
--- a/docs/compilation.md
+++ b/docs/compilation.md
@@ -143,34 +143,52 @@ 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
+g++ -std=c++11 -shared -fPIC c/lib.cpp -I ./include/  -I ./fp16/include/ -I ./robin-map/include/ -o libusearch_c.a
 ```
 
+
+## 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
+make -C ./c libusearch_c.so && mv ./c/libusearch_c.so ./golang/libusearch_c.so 
+cd golang && go test -v ; cd ..
+```
+
+## Wolfram
+
+```sh
+brew install --cask wolfram-engine
+```
+
+
 ## Docker
 
 ```sh
diff --git a/docs/wolfram/index.rst b/docs/wolfram/index.rst
new file mode 100644
index 00000000..bb4c6e8d
--- /dev/null
+++ b/docs/wolfram/index.rst
@@ -0,0 +1,6 @@
+=====================
+Wolfram SDK
+=====================
+
+
+.. mdinclude:: ../../wolfram/README.md
diff --git a/golang/lib.go b/golang/lib.go
index caefbfcd..39739e84 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${SRCDIR}/. -Wl,-rpath,$SRCDIR/. -lusearch_c
+#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..3be878f0 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 {
@@ -468,6 +468,21 @@ template <typename allocator_at = std::allocator<byte_t>> class visits_bitset_gt
     }
 
 #endif
+
+    class lock_t {
+        visits_bitset_gt& bitset_;
+        std::size_t bit_offset_;
+
+      public:
+        inline ~lock_t() noexcept { bitset_.atomic_reset(bit_offset_); }
+        inline lock_t(visits_bitset_gt& bitset, std::size_t bit_offset) noexcept
+            : bitset_(bitset), bit_offset_(bit_offset) {
+            while (bitset_.atomic_set(bit_offset_))
+                ;
+        }
+    };
+
+    inline lock_t lock(std::size_t i) noexcept { return {*this, i}; }
 };
 
 using visits_bitset_t = visits_bitset_gt<>;
@@ -962,7 +977,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 +1024,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 +1099,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 +1113,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 +1242,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 +1255,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 +1292,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 +1309,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 +1353,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 +1366,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 +1429,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 +1441,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 +1499,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 +1700,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 +1708,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 +1718,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 +1945,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 +1975,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 {}
@@ -1934,7 +2025,7 @@ class index_gt {
                 std::memmove(distances + offset + 1, distances + offset, count_worse * sizeof(distance_t));
                 keys[offset] = result.member.key;
                 distances[offset] = result.distance;
-                merged_count = (std::min)(merged_count + 1u, max_count);
+                merged_count += merged_count != max_count;
             }
             return merged_count;
         }
@@ -1972,10 +2063,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 +2132,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 +2152,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 +2171,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 +2215,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 +2242,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 +2263,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 +2278,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 +2289,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 +2345,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 +2371,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 +2402,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 +2411,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 +2427,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 +2496,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 +2558,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 +2679,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 +2693,7 @@ class index_gt {
                         neighbors.push_back(neighbor_slot);
                 }
             }
+            progress(node_idx, nodes_count);
         });
     }
 
@@ -2542,27 +2775,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 +2806,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 +2820,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 +2858,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 +2870,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 +2949,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 +2976,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 +2990,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 +3015,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 +3044,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 +3058,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 +3078,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 +3092,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 +3106,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 +3134,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 +3176,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 +3286,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 +3320,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 +3382,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..38c0ab23 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_; }
@@ -475,12 +476,14 @@ class index_dense_gt {
 
     // The metric and its properties
     metric_t const& metric() const { return metric_; }
+    void change_metric(metric_t metric) { metric_ = std::move(metric); }
+
     scalar_kind_t scalar_kind() const noexcept { return metric_.scalar_kind(); }
     std::size_t bytes_per_vector() const noexcept { return metric_.bytes_per_vector(); }
     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 +499,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 +567,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 +585,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 +599,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 +630,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 +647,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 +675,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 +732,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 +779,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 +859,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 +868,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 +904,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 +935,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 +950,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 +1029,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 +1040,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 +1066,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 +1097,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 +1110,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 +1161,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 +1196,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 +1214,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 +1225,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 +1242,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 +1251,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 +1267,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 +1306,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 +1327,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 +1346,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..afdb1bb6 100644
--- a/javascript/lib.cpp
+++ b/javascript/lib.cpp
@@ -37,6 +37,8 @@ class Index : public Napi::ObjectWrap<Index> {
 
     void Add(Napi::CallbackInfo const& ctx);
     Napi::Value Search(Napi::CallbackInfo const& ctx);
+    Napi::Value Remove(Napi::CallbackInfo const& ctx);
+    Napi::Value Contains(Napi::CallbackInfo const& ctx);
 
     std::unique_ptr<index_dense_t> native_;
 };
@@ -51,6 +53,8 @@ Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
             InstanceMethod("connectivity", &Index::GetConnectivity),
             InstanceMethod("add", &Index::Add),
             InstanceMethod("search", &Index::Search),
+            InstanceMethod("remove", &Index::Remove),
+            InstanceMethod("contains", &Index::Contains),
             InstanceMethod("save", &Index::Save),
             InstanceMethod("load", &Index::Load),
             InstanceMethod("view", &Index::View),
@@ -74,8 +78,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 +89,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 +131,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 +203,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 +239,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 +253,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();
@@ -272,6 +295,64 @@ Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
     }
 }
 
+Napi::Value Index::Remove(Napi::CallbackInfo const& ctx) {
+    Napi::Env env = ctx.Env();
+    if (ctx.Length() < 1 || !ctx[0].IsBigInt()) {
+        Napi::TypeError::New(env, "Expects an entry identifier").ThrowAsJavaScriptException();
+        return {};
+    }
+
+    Napi::BigInt key_js = ctx[0].As<Napi::BigInt>();
+    bool lossless = true;
+    std::uint64_t key = key_js.Uint64Value(&lossless);
+    if (!lossless) {
+        Napi::TypeError::New(env, "Identifier must be an unsigned integer").ThrowAsJavaScriptException();
+        return {};
+    }
+
+    try {
+        auto result = native_->remove(key);
+        if (!result) {
+            Napi::TypeError::New(env, "Removal has failed").ThrowAsJavaScriptException();
+            return {};
+        }
+        return Napi::Boolean::New(env, result.completed);
+    } catch (std::bad_alloc const&) {
+        Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
+        return {};
+    } catch (...) {
+        Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
+        return {};
+    }
+}
+
+Napi::Value Index::Contains(Napi::CallbackInfo const& ctx) {
+    Napi::Env env = ctx.Env();
+    if (ctx.Length() < 1 || !ctx[0].IsBigInt()) {
+        Napi::TypeError::New(env, "Expects an entry identifier").ThrowAsJavaScriptException();
+        return {};
+    }
+
+    Napi::BigInt key_js = ctx[0].As<Napi::BigInt>();
+    bool lossless = true;
+    std::uint64_t key = key_js.Uint64Value(&lossless);
+    if (!lossless) {
+        Napi::TypeError::New(env, "Identifier must be an unsigned integer").ThrowAsJavaScriptException();
+        return {};
+    }
+
+    try {
+        bool result = native_->contains(key);
+        return Napi::Boolean::New(env, result);
+    } catch (std::bad_alloc const&) {
+        Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
+        return {};
+    } catch (...) {
+        Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
+        return {};
+    }
+}
+
 Napi::Object InitAll(Napi::Env env, Napi::Object exports) { return Index::Init(env, exports); }
 
 NODE_API_MODULE(usearch, InitAll)
diff --git a/javascript/usearch.d.ts b/javascript/usearch.d.ts
index 6b5dd6ca..bd7a6bd1 100644
--- a/javascript/usearch.d.ts
+++ b/javascript/usearch.d.ts
@@ -1,53 +1,53 @@
 
 /** 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. */
 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,33 @@ 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: bigint): Matches;
+
+    /** 
+     * Check if an entry is contained in the index.
+     * 
+     * @param {bigint} key Identifier to look up.
      */
-    search(mat: Float32Array, k: number): SearchResults;
+    contains(key: bigint): boolean;
+
+    /** 
+     * Remove a vector from the index.
+     * 
+     * @param {bigint} key Input identifier for every vector to be removed.
+     */
+    remove(key: bigint): boolean;
+
 }
\ 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/package.json b/package.json
index 4bd7da53..f5416cdc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "usearch",
-    "version": "0.22.3",
+    "version": "1.1.0",
     "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
     "author": "Ash Vardanian",
     "license": "Apache 2.0",
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..b238d65d 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);
@@ -144,10 +146,10 @@ metric_t udf(metric_kind_t kind, metric_signature_t signature, std::uintptr_t me
 static dense_index_py_t make_index(      //
     std::size_t dimensions,              //
     scalar_kind_t scalar_kind,           //
-    metric_kind_t metric_kind,           //
     std::size_t connectivity,            //
     std::size_t expansion_add,           //
     std::size_t expansion_search,        //
+    metric_kind_t metric_kind,           //
     metric_signature_t metric_signature, //
     std::uintptr_t metric_uintptr) {
 
@@ -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>();
@@ -296,7 +298,10 @@ static void search_typed(                                       //
     if (!threads)
         threads = std::thread::hardware_concurrency();
 
-    std::vector<std::mutex> vectors_mutexes(static_cast<std::size_t>(vectors_count));
+    visits_bitset_t query_mutexes(static_cast<std::size_t>(vectors_count));
+    if (!query_mutexes)
+        throw std::bad_alloc();
+
     executor_default_t{threads}.execute_bulk(indexes.shards_.size(), [&](std::size_t, std::size_t task_idx) {
         dense_index_py_t& index = *indexes.shards_[task_idx].get();
 
@@ -316,7 +321,7 @@ static void search_typed(                                       //
             dense_search_result_t result = index.search(vector, wanted, config);
             result.error.raise();
             {
-                std::unique_lock<std::mutex> lock(vectors_mutexes[vector_idx]);
+                auto lock = query_mutexes.lock(vector_idx);
                 counts_py1d(vector_idx) = static_cast<Py_ssize_t>(result.merge_into( //
                     &keys_py2d(vector_idx, 0),                                       //
                     &distances_py2d(vector_idx, 0),                                  //
@@ -324,8 +329,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();
         }
@@ -346,7 +351,7 @@ static py::tuple search_many_in_index( //
     index_at& index, py::buffer vectors, std::size_t wanted, bool exact, std::size_t threads) {
 
     if (wanted == 0)
-        return py::tuple(3);
+        return py::tuple(5);
 
     if (index.limits().threads_search < threads)
         throw std::invalid_argument("Can't use that many threads!");
@@ -363,16 +368,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 +386,125 @@ 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;
+}
+
+template <typename scalar_at>
+static void search_typed_brute_force(                                //
+    py::buffer_info& dataset_info, py::buffer_info& queries_info,    //
+    std::size_t wanted, std::size_t threads, metric_t const& metric, //
+    py::array_t<key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py) {
+
+    auto keys_py2d = keys_py.template mutable_unchecked<2>();
+    auto distances_py2d = distances_py.template mutable_unchecked<2>();
+    auto counts_py1d = counts_py.template mutable_unchecked<1>();
+
+    std::size_t dataset_count = static_cast<std::size_t>(dataset_info.shape[0]);
+    std::size_t queries_count = static_cast<std::size_t>(queries_info.shape[0]);
+    std::size_t dimensions = static_cast<std::size_t>(dataset_info.shape[1]);
+
+    byte_t const* dataset_data = reinterpret_cast<byte_t const*>(dataset_info.ptr);
+    byte_t const* queries_data = reinterpret_cast<byte_t const*>(queries_info.ptr);
+    for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx)
+        counts_py1d(query_idx) = 0;
+
+    if (!threads)
+        threads = std::thread::hardware_concurrency();
+
+    std::size_t tasks_count = static_cast<std::size_t>(dataset_count * queries_count);
+    visits_bitset_t query_mutexes(static_cast<std::size_t>(queries_count));
+    if (!query_mutexes)
+        throw std::bad_alloc();
+
+    executor_default_t{threads}.execute_bulk(tasks_count, [&](std::size_t, std::size_t task_idx) {
+        //
+        std::size_t dataset_idx = task_idx / queries_count;
+        std::size_t query_idx = task_idx % queries_count;
+
+        byte_t const* dataset = dataset_data + dataset_idx * dataset_info.strides[0];
+        byte_t const* query = queries_data + query_idx * queries_info.strides[0];
+        distance_t distance = metric(dataset, query);
+
+        {
+            auto lock = query_mutexes.lock(query_idx);
+            key_t* keys = &keys_py2d(query_idx, 0);
+            distance_t* distances = &distances_py2d(query_idx, 0);
+            std::size_t& matches = reinterpret_cast<std::size_t&>(counts_py1d(query_idx));
+            if (matches == wanted)
+                if (distances[wanted - 1] <= distance)
+                    return;
+
+            std::size_t offset = std::lower_bound(distances, distances + matches, distance) - distances;
+
+            std::size_t count_worse = matches - offset - (wanted == matches);
+            std::memmove(keys + offset + 1, keys + offset, count_worse * sizeof(key_t));
+            std::memmove(distances + offset + 1, distances + offset, count_worse * sizeof(distance_t));
+            keys[offset] = static_cast<key_t>(dataset_idx);
+            distances[offset] = distance;
+            matches += matches != wanted;
+        }
+
+        if (PyErr_CheckSignals() != 0)
+            throw py::error_already_set();
+    });
+}
+
+static py::tuple search_many_brute_force(    //
+    py::buffer dataset, py::buffer queries,  //
+    std::size_t wanted, std::size_t threads, //
+    metric_kind_t metric_kind,               //
+    metric_signature_t metric_signature,     //
+    std::uintptr_t metric_uintptr) {
+
+    if (wanted == 0)
+        return py::tuple(5);
+
+    py::buffer_info dataset_info = dataset.request();
+    py::buffer_info queries_info = queries.request();
+    if (dataset_info.ndim != 2 || queries_info.ndim != 2)
+        throw std::invalid_argument("Expects a matrix of dataset to add!");
+
+    Py_ssize_t dataset_count = dataset_info.shape[0];
+    Py_ssize_t dataset_dimensions = dataset_info.shape[1];
+    Py_ssize_t queries_count = queries_info.shape[0];
+    Py_ssize_t queries_dimensions = queries_info.shape[1];
+    if (dataset_dimensions != queries_dimensions)
+        throw std::invalid_argument("The number of vector dimensions doesn't match!");
+
+    scalar_kind_t dataset_kind = numpy_string_to_kind(dataset_info.format);
+    scalar_kind_t queries_kind = numpy_string_to_kind(queries_info.format);
+    if (dataset_kind != queries_kind)
+        throw std::invalid_argument("The types of vectors don't match!");
+
+    py::array_t<key_t> keys_py({dataset_count, static_cast<Py_ssize_t>(wanted)});
+    py::array_t<distance_t> distances_py({dataset_count, static_cast<Py_ssize_t>(wanted)});
+    py::array_t<Py_ssize_t> counts_py(dataset_count);
+
+    std::size_t dimensions = static_cast<std::size_t>(queries_dimensions);
+    metric_t metric =  //
+        metric_uintptr //
+            ? udf(metric_kind, metric_signature, metric_uintptr, queries_kind, dimensions)
+            : metric_t(dimensions, metric_kind, queries_kind);
+
+    // clang-format off
+    switch (dataset_kind) {
+    case scalar_kind_t::b1x8_k: search_typed_brute_force<b1x8_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
+    case scalar_kind_t::i8_k: search_typed_brute_force<i8_bits_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
+    case scalar_kind_t::f16_k: search_typed_brute_force<f16_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
+    case scalar_kind_t::f32_k: search_typed_brute_force<f32_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
+    case scalar_kind_t::f64_k: search_typed_brute_force<f64_t>(dataset_info, queries_info, wanted, threads, metric, keys_py, distances_py, counts_py); break;
+    default: throw std::invalid_argument("Incompatible vector types: " + dataset_info.format);
+    }
+    // clang-format on
+
+    py::tuple results(5);
+    results[0] = keys_py;
+    results[1] = distances_py;
+    results[2] = counts_py;
+    results[3] = 0;
+    results[4] = static_cast<std::size_t>(dataset_count * queries_count);
     return results;
 }
 
@@ -391,21 +513,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 +537,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 +574,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 +624,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("IP", metric_kind_t::cos_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::cos_k);
 
     py::enum_<scalar_kind_t>(m, "ScalarKind")
         .value("Unknown", scalar_kind_t::unknown_k)
@@ -548,16 +682,27 @@ PYBIND11_MODULE(compiled, m) {
         return result;
     });
 
+    m.def("exact_search", &search_many_brute_force,                        //
+          py::arg("dataset"),                                              //
+          py::arg("queries"),                                              //
+          py::arg("count") = 10,                                           //
+          py::kw_only(),                                                   //
+          py::arg("threads") = 0,                                          //
+          py::arg("metric_kind") = metric_kind_t::cos_k,                   //
+          py::arg("metric_signature") = metric_signature_t::array_array_k, //
+          py::arg("metric_pointer") = 0                                    //
+    );
+
     auto i = py::class_<dense_index_py_t, std::shared_ptr<dense_index_py_t>>(m, "Index");
 
     i.def(py::init(&make_index),                                           //
           py::kw_only(),                                                   //
           py::arg("ndim") = 0,                                             //
           py::arg("dtype") = scalar_kind_t::f32_k,                         //
-          py::arg("metric_kind") = metric_kind_t::ip_k,                    //
           py::arg("connectivity") = default_connectivity(),                //
           py::arg("expansion_add") = default_expansion_add(),              //
           py::arg("expansion_search") = default_expansion_search(),        //
+          py::arg("metric_kind") = metric_kind_t::cos_k,                   //
           py::arg("metric_signature") = metric_signature_t::array_array_k, //
           py::arg("metric_pointer") = 0                                    //
     );
@@ -601,7 +746,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 +764,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"));
@@ -639,6 +784,23 @@ PYBIND11_MODULE(compiled, m) {
     i.def_property("expansion_add", &dense_index_py_t::expansion_add, &dense_index_py_t::change_expansion_add);
     i.def_property("expansion_search", &dense_index_py_t::expansion_search, &dense_index_py_t::change_expansion_search);
 
+    i.def(
+        "change_metric",
+        [](dense_index_py_t& index, metric_kind_t metric_kind, metric_signature_t metric_signature,
+           std::uintptr_t metric_uintptr) {
+            scalar_kind_t scalar_kind = index.scalar_kind();
+            std::size_t dimensions = index.dimensions();
+            metric_t metric =  //
+                metric_uintptr //
+                    ? udf(metric_kind, metric_signature, metric_uintptr, scalar_kind, dimensions)
+                    : metric_t(dimensions, metric_kind, scalar_kind);
+            index.change_metric(std::move(metric));
+        },
+        py::arg("metric_kind") = metric_kind_t::cos_k,                   //
+        py::arg("metric_signature") = metric_signature_t::array_array_k, //
+        py::arg("metric_pointer") = 0                                    //
+    );
+
     i.def_property_readonly("keys", &get_all_keys<dense_index_py_t>);
     i.def("get_keys", &get_keys<dense_index_py_t>, py::arg("offset") = 0,
           py::arg("limit") = std::numeric_limits<std::size_t>::max());
@@ -651,6 +813,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..d8cd5afb 100644
--- a/python/usearch/index.py
+++ b/python/usearch/index.py
@@ -16,7 +16,8 @@
 from usearch.compiled import Indexes as _CompiledIndexes
 from usearch.compiled import IndexStats as _CompiledIndexStats
 
-from usearch.compiled import index_dense_metadata
+from usearch.compiled import index_dense_metadata as _index_dense_metadata
+from usearch.compiled import exact_search as _exact_search
 from usearch.compiled import MetricKind, ScalarKind, MetricSignature
 from usearch.compiled import (
     DEFAULT_CONNECTIVITY,
@@ -36,7 +37,7 @@
 Key = np.uint64
 
 
-def _normalize_dtype(dtype, metric: MetricKind = MetricKind.IP) -> ScalarKind:
+def _normalize_dtype(dtype, metric: MetricKind = MetricKind.Cos) -> ScalarKind:
     if dtype is None or dtype == "":
         return ScalarKind.B1 if metric in MetricKindBitwise else ScalarKind.F32
 
@@ -50,14 +51,16 @@ 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,
+        "float64": ScalarKind.F64,
+        "float32": ScalarKind.F32,
+        "float16": ScalarKind.F16,
+        "int8": ScalarKind.I8,
     }
+    if isinstance(dtype, np.dtype):
+        dtype = dtype.name
     return _normalize[dtype]
 
 
@@ -66,7 +69,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 +80,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]
@@ -85,12 +88,16 @@ def _to_numpy_dtype(dtype: ScalarKind):
 
 def _normalize_metric(metric):
     if metric is None:
-        return MetricKind.IP
+        return MetricKind.Cos
 
     if isinstance(metric, str):
         _normalize = {
             "cos": MetricKind.Cos,
+            "cosine": MetricKind.Cos,
             "ip": MetricKind.IP,
+            "dot": MetricKind.IP,
+            "inner_product": MetricKind.IP,
+            "l2sq": MetricKind.L2sq,
             "l2_sq": MetricKind.L2sq,
             "haversine": MetricKind.Haversine,
             "pearson": MetricKind.Pearson,
@@ -120,7 +127,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 +258,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 +293,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 +304,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 +315,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)"
@@ -342,8 +358,9 @@ class Index:
 
     def __init__(
         self,
+        *,
         ndim: int = 0,
-        metric: Union[str, MetricKind, CompiledMetric] = MetricKind.IP,
+        metric: Union[str, MetricKind, CompiledMetric] = MetricKind.Cos,
         dtype: Optional[Union[str, ScalarKind]] = None,
         connectivity: Optional[int] = None,
         expansion_add: Optional[int] = None,
@@ -360,16 +377,15 @@ def __init__(
             coordinates. Angular (Cos) and Euclidean (L2sq), obviously, apply to
             vectors with arbitrary number of dimensions.
 
-        :param metric: Distance function, defaults to MetricKind.IP
+        :param metric: Distance function, defaults to MetricKind.Cos
         :type metric: Union[MetricKind, Callable, str], optional
             Kind of the distance function, or the Numba `cfunc` JIT-compiled object.
             Possible `MetricKind` values: IP, Cos, L2sq, Haversine, Pearson,
             Hamming, Tanimoto, Sorensen.
-            Not every kind is JIT-able. For Jaccard distance, use `SparseIndex`.
 
         :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.
@@ -430,12 +446,12 @@ def __init__(
         self._compiled = _CompiledIndex(
             ndim=ndim,
             dtype=dtype,
-            metric_kind=self._metric_kind,
-            metric_pointer=self._metric_pointer,
-            metric_signature=self._metric_signature,
             connectivity=connectivity,
             expansion_add=expansion_add,
             expansion_search=expansion_search,
+            metric_kind=self._metric_kind,
+            metric_pointer=self._metric_pointer,
+            metric_signature=self._metric_signature,
         )
 
         self.path = path
@@ -450,7 +466,7 @@ def metadata(path: os.PathLike) -> Optional[dict]:
         if not os.path.exists(path):
             return None
         try:
-            return index_dense_metadata(path)
+            return _index_dense_metadata(path)
         except Exception:
             return None
 
@@ -492,9 +508,9 @@ def add(
         primary vectors store during the process of construction.
 
         :param keys: Unique identifier(s) for passed vectors, optional
-        :type keys: Buffer
+        :type keys: np.ndarray
         :param vectors: Vector or a row-major matrix
-        :type vectors: Buffer
+        :type vectors: np.ndarray
         :param copy: Should the index store a copy of vectors, defaults to True
         :type copy: bool, optional
         :param threads: Optimal number of cores to use, defaults to 0
@@ -530,7 +546,7 @@ def search(
         Performs approximate nearest neighbors search for one or more queries.
 
         :param vectors: Query vector or vectors.
-        :type vectors: Buffer
+        :type vectors: np.ndarray
         :param k: Upper limit on the number of matches to find, defaults to 10
         :type k: int, optional
         :param threads: Optimal number of cores to use, defaults to 0
@@ -541,7 +557,7 @@ def search(
         :type log: Union[str, bool], optional
         :param batch_size: Number of vectors to process at once, defaults to 0
         :type batch_size: int, optional
-        :return: Approximate matches for one or more queries
+        :return: Matches for one or more queries
         :rtype: Union[Matches, BatchMatches]
         """
 
@@ -569,7 +585,7 @@ def remove(
         In other cases, rebuilding - is the recommended approach.
 
         :param keys: Unique identifier for passed vectors, optional
-        :type keys: Buffer
+        :type keys: np.ndarray
         :param compact: Removes links to removed nodes (expensive), defaults to False
         :type compact: bool, optional
         :param threads: Optimal number of cores to use, defaults to 0
@@ -632,6 +648,28 @@ def ndim(self) -> int:
     def metric(self) -> Union[MetricKind, CompiledMetric]:
         return self._metric_jit if self._metric_jit else self._metric_kind
 
+    @metric.setter
+    def metric(self, metric: Union[str, MetricKind, CompiledMetric]):
+        metric = _normalize_metric(metric)
+        if isinstance(metric, MetricKind):
+            metric_kind = metric
+            metric_pointer = 0
+            metric_signature = MetricSignature.ArrayArraySize
+        elif isinstance(metric, CompiledMetric):
+            metric_kind = metric.kind
+            metric_pointer = metric.pointer
+            metric_signature = metric.signature
+        else:
+            raise ValueError(
+                "The `metric` must be a `CompiledMetric` or a `MetricKind`"
+            )
+
+        return self._compiled.change_metric(
+            metric_kind=metric_kind,
+            metric_pointer=metric_pointer,
+            metric_signature=metric_signature,
+        )
+
     @property
     def dtype(self) -> ScalarKind:
         return self._compiled.dtype
@@ -791,6 +829,9 @@ def level_stats(self, level: int) -> _CompiledIndexStats:
         """
         return self._compiled.level_stats(level)
 
+    def __del__(self):
+        self.reset()
+
     def __repr__(self) -> str:
         f = "usearch.Index({} x {}, {}, expansion: {} & {}, {} vectors in {} levels)"
         return f.format(
@@ -860,3 +901,108 @@ def search(
             log=False,
             batch_size=None,
         )
+
+
+def search(
+    dataset: np.ndarray,
+    query: np.ndarray,
+    k: int = 10,
+    metric: Union[str, MetricKind, CompiledMetric] = MetricKind.Cos,
+    *,
+    exact: bool = False,
+    threads: int = 0,
+    log: Union[str, bool] = False,
+    batch_size: int = 0,
+) -> Union[Matches, BatchMatches]:
+    """Shortcut for search, that can avoid index construction. Particularly useful for
+    tiny datasets, where brute-force exact search works fast enough.
+
+    :param dataset: Row-major matrix.
+    :type dataset: np.ndarray
+    :param query: Query vector or vectors (also row-major), to find in `dataset`.
+    :type query: np.ndarray
+
+    :param k: Upper limit on the number of matches to find, defaults to 10
+    :type k: int, optional
+
+    :param metric: Distance function, defaults to MetricKind.Cos
+    :type metric: Union[MetricKind, Callable, str], optional
+        Kind of the distance function, or the Numba `cfunc` JIT-compiled object.
+        Possible `MetricKind` values: IP, Cos, L2sq, Haversine, Pearson,
+        Hamming, Tanimoto, Sorensen.
+
+    :param threads: Optimal number of cores to use, defaults to 0
+    :type threads: int, optional
+    :param exact: Perform exhaustive linear-time exact search, defaults to False
+    :type exact: bool, optional
+    :param log: Whether to print the progress bar, default to False
+    :type log: Union[str, bool], optional
+    :param batch_size: Number of vectors to process at once, defaults to 0
+    :type batch_size: int, optional
+
+    :return: Matches for one or more queries
+    :rtype: Union[Matches, BatchMatches]
+    """
+    assert dataset.ndim == 2, "Dataset must be a matrix, with a vector in each row"
+
+    if not exact:
+        index = Index(
+            dataset.shape[1],
+            metric=metric,
+            dtype=dataset.dtype,
+        )
+        index.add(
+            None,
+            dataset,
+            threads=threads,
+            log=log,
+            batch_size=batch_size,
+        )
+        return index.search(
+            query,
+            k,
+            threads=threads,
+            log=log,
+            batch_size=batch_size,
+        )
+
+    metric = _normalize_metric(metric)
+    if isinstance(metric, MetricKind):
+        metric_kind = metric
+        metric_pointer = 0
+        metric_signature = MetricSignature.ArrayArraySize
+    elif isinstance(metric, CompiledMetric):
+        metric_kind = metric.kind
+        metric_pointer = metric.pointer
+        metric_signature = metric.signature
+    else:
+        raise ValueError("The `metric` must be a `CompiledMetric` or a `MetricKind`")
+
+    class WrappedDataset:
+        def __init__(self) -> None:
+            pass
+
+        def search(self, query, k, **kwargs):
+            kwargs.pop("exact")
+            kwargs.update(
+                dict(
+                    metric_kind=metric_kind,
+                    metric_pointer=metric_pointer,
+                    metric_signature=metric_signature,
+                )
+            )
+            assert dataset.shape[1] == query.shape[1], "Number of dimensions differs"
+            if dataset.dtype != query.dtype:
+                query = query.astype(dataset.dtype)
+
+            return _exact_search(dataset, query, k, **kwargs)
+
+    return _search_in_compiled(
+        compiled=WrappedDataset(),
+        vectors=query,
+        k=k,
+        threads=threads,
+        exact=True,
+        log=log,
+        batch_size=batch_size,
+    )
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..603cc5b7 100644
--- a/rust/lib.cpp
+++ b/rust/lib.cpp
@@ -4,14 +4,15 @@
 using namespace unum::usearch;
 using namespace unum;
 
-using index_t = typename Index::index_t;
+using index_t = index_dense_t;
 using add_result_t = typename index_t::add_result_t;
 using search_result_t = typename index_t::search_result_t;
+using labeling_result_t = typename index_t::labeling_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();
@@ -28,14 +29,22 @@ Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, s
     config.expansion = index_->expansion_search();
     search_result_t result = index_->search(vector.data(), count, config);
     result.error.raise();
-    matches.count = result.dump_to(matches.keys.data(), matches.distances.data());
-    matches.keys.truncate(matches.count);
-    matches.distances.truncate(matches.count);
+    count = result.dump_to(matches.keys.data(), matches.distances.data());
+    matches.keys.truncate(count);
+    matches.distances.truncate(count);
     return matches;
 }
 
 void Index::add(key_t key, rust::Slice<float const> vector) const { index_->add(key, vector.data()).error.raise(); }
 
+bool Index::remove(key_t key) const {
+    labeling_result_t result = index_->remove(key);
+    result.error.raise();
+    return result.completed;
+}
+
+bool Index::contains(key_t key) const { return index_->contains(key); }
+
 Matches Index::search(rust::Slice<float const> vector, size_t count) const {
     Matches matches;
     matches.keys.reserve(count);
@@ -44,9 +53,9 @@ Matches Index::search(rust::Slice<float const> vector, size_t count) const {
         matches.keys.push_back(0), matches.distances.push_back(0);
     search_result_t result = index_->search(vector.data(), count);
     result.error.raise();
-    matches.count = result.dump_to(matches.keys.data(), matches.distances.data());
-    matches.keys.truncate(matches.count);
-    matches.distances.truncate(matches.count);
+    count = result.dump_to(matches.keys.data(), matches.distances.data());
+    matches.keys.truncate(count);
+    matches.distances.truncate(count);
     return matches;
 }
 
@@ -74,7 +83,7 @@ std::unique_ptr<Index> wrap(index_t&& index) {
 metric_kind_t rust_to_cpp_metric(MetricKind value) {
     switch (value) {
     case MetricKind::IP: return metric_kind_t::ip_k;
-    case MetricKind::L2Sq: return metric_kind_t::l2sq_k;
+    case MetricKind::L2sq: return metric_kind_t::l2sq_k;
     case MetricKind::Cos: return metric_kind_t::cos_k;
     case MetricKind::Pearson: return metric_kind_t::pearson_k;
     case MetricKind::Haversine: return metric_kind_t::haversine_k;
@@ -87,7 +96,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.hpp b/rust/lib.hpp
index 42192b7c..705f0d17 100644
--- a/rust/lib.hpp
+++ b/rust/lib.hpp
@@ -27,6 +27,9 @@ class Index {
     Matches search(rust::Slice<float const> vector, size_t count) const;
     Matches search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const;
 
+    bool remove(key_t key) const;
+    bool contains(key_t key) const;
+
     size_t dimensions() const;
     size_t connectivity() const;
     size_t size() const;
diff --git a/rust/lib.rs b/rust/lib.rs
index 76969008..1c9eb9d2 100644
--- a/rust/lib.rs
+++ b/rust/lib.rs
@@ -3,14 +3,13 @@ pub mod ffi {
     
     // Shared structs with fields visible to both languages.
     struct Matches {
-        count: usize,
         keys: Vec<u64>,
         distances: Vec<f32>,
     }
 
     enum MetricKind {
         IP,
-        L2Sq,
+        L2sq,
         Cos,
         Pearson,
         Haversine,
@@ -23,7 +22,7 @@ pub mod ffi {
         F64,
         F32,
         F16,
-        F8,
+        I8,
         B1,
     }
 
@@ -53,6 +52,8 @@ pub mod ffi {
 
         pub fn add(self: &Index, key: u64, vector: &[f32]) -> Result<()>;
         pub fn search(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
+        pub fn remove(self: &Index, key: u64) -> Result<bool>;
+        pub fn contains(self: &Index, key: u64) -> bool;
 
         pub fn save(self: &Index, path: &str) -> Result<()>;
         pub fn load(self: &Index, path: &str) -> Result<()>;
@@ -97,7 +98,7 @@ mod tests {
     
         // Read back the tags
         let results = index.search(&first, 10).unwrap();
-        assert_eq!(results.count, 2);
+        assert_eq!(results.keys.len(), 2);
     
         // Validate serialization
         assert!(index.save("index.rust.usearch").is_ok());
@@ -106,7 +107,7 @@ mod tests {
     
         // Make sure every function is called at least once
         assert!(new_index(&options).is_ok());
-        options.metric = MetricKind::L2Sq;
+        options.metric = MetricKind::L2sq;
         assert!(new_index(&options).is_ok());
         options.metric = MetricKind::Cos;
         assert!(new_index(&options).is_ok());
diff --git a/wasmer.toml b/wasmer.toml
index 4ea77da8..883e79a2 100644
--- a/wasmer.toml
+++ b/wasmer.toml
@@ -1,6 +1,6 @@
 [package]
 name="unum/usearch"
-version="0.22.3"
+version="1.1.0"
 description="Smaller & Faster Single-File Vector Search Engine from Unum"
 license="Apache-2.0"
 readme="README.md"


Test patch:
diff --git a/.github/workflows/csharp-build-and-test.yml b/.github/workflows/csharp-build-and-test.yml
new file mode 100644
index 00000000..70600f79
--- /dev/null
+++ b/.github/workflows/csharp-build-and-test.yml
@@ -0,0 +1,49 @@
+# This workflow builds USearch native libraries, then build and test C# USearch wrapper
+# Currently for development purposes an if condition specified below to work only fork 
+
+name: Test C# wrapper
+
+on:
+  push:
+    branches: ["160-feature-c-bindings-and-semantic-kernel-integration"]
+
+env:
+  LINUX_OSX_SCRIPT: build_and_test.sh
+  WINDOWS_SCRIPT: build_and_test.cmd
+
+jobs:
+  matrix-test:
+      if: github.repository == 'AleksandrKent/usearch'
+      runs-on: ${{ matrix.os }}  
+      strategy:
+        fail-fast: false
+        matrix:
+          os: [ubuntu-latest, windows-latest, macos-latest]
+      steps:
+        - uses: actions/checkout@v3
+          with:
+            ref: "160-feature-c-bindings-and-semantic-kernel-integration"
+        - run: |
+            git submodule update --init --recursive
+          shell: bash
+
+        - name: Setup .NET
+          uses: actions/setup-dotnet@v3
+          with:
+            dotnet-version: 6.0.x
+
+        # Ubuntu and macOS steps
+        - name: Run ${{ env.LINUX_OSX_SCRIPT }} on Ubuntu or macOS
+          if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
+          run: |
+            cd ./csharp
+            "./${{ env.LINUX_OSX_SCRIPT }}"
+          shell: bash
+        
+        # Windows step
+        - name: Run ${{ env.WINDOWS_SCRIPT }} on Windows
+          if: matrix.os == 'windows-latest'
+          run: |
+            cd .\csharp
+            ${{ env.WINDOWS_SCRIPT }}
+          shell: cmd
\ No newline at end of file
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/csharp/build_and_test.cmd b/csharp/build_and_test.cmd
new file mode 100644
index 00000000..3a0f4943
--- /dev/null
+++ b/csharp/build_and_test.cmd
@@ -0,0 +1,55 @@
+@echo off
+setlocal EnableDelayedExpansion
+
+:: Get the absolute path of the directory where the script is located
+set SCRIPT_DIR=%~dp0
+set SCRIPT_DIR=%SCRIPT_DIR:~0,-1%
+
+:: Set up project paths
+set PROJECT_ROOT=%SCRIPT_DIR%\..
+set C_DIR=%PROJECT_ROOT%\c
+set CSHARP_DIR=%PROJECT_ROOT%\csharp
+set TEST_PROJ_PATH=%CSHARP_DIR%\src\LibUSearch.Tests
+set TEST_PROJ_RUNTIMES_TEMP_PATH=%TEST_PROJ_PATH%\runtimes
+
+:: Build the native DLL
+cd %C_DIR%
+if not exist makefile (
+    echo Makefile not found in %C_DIR%
+    exit /b 1
+)
+make libusearch_c.dll
+if %ERRORLEVEL% neq 0 (
+    echo Build failed!
+    exit /b 1
+)
+
+:: Switch to the C# directory
+cd %CSHARP_DIR%
+
+:: Clean and build the .NET project
+dotnet clean
+dotnet build --configuration Release
+if %ERRORLEVEL% neq 0 (
+    echo Build failed!
+    exit /b 1
+)
+
+:: Create a temporary path for the DLL and move it there
+mkdir "%TEST_PROJ_RUNTIMES_TEMP_PATH%"
+move "%C_DIR%\libusearch_c.dll" "%TEST_PROJ_RUNTIMES_TEMP_PATH%"
+
+:: Run the tests
+dotnet test --configuration Release
+set EXIT_CODE=%ERRORLEVEL%
+
+:: Clear the temporary path
+rmdir /s /q "%TEST_PROJ_RUNTIMES_TEMP_PATH%"
+
+if %EXIT_CODE% neq 0 (
+    echo Tests failed!
+    exit /b 1
+) else (
+    echo Tests passed successfully!
+    exit /b 0
+)
\ No newline at end of file
diff --git a/csharp/build_and_test.sh b/csharp/build_and_test.sh
new file mode 100755
index 00000000..8eed4d1a
--- /dev/null
+++ b/csharp/build_and_test.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+# Get absolute path to the usearch dir
+# FIX: realpath is not available on OSX, so use pwd
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+# Define project paths
+PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
+C_DIR="$PROJECT_ROOT/c"
+CSHARP_DIR="$PROJECT_ROOT/csharp"
+TEST_PROJ_PATH="$CSHARP_DIR/src/LibUSearch.Tests"
+TEST_PROJ_RUNTIMES_TEMP_PATH="$TEST_PROJ_PATH/runtimes"
+
+# Determine the OS type (linux or macos) and set the shared library extension accordingly
+case "$(uname -s)" in
+    Darwin)
+        SHARED_LIB_EXT="dylib"
+        ;;
+    Linux)
+        SHARED_LIB_EXT="so"
+        ;;
+    *)
+        echo "Unsupported operating system"
+        exit 1
+        ;;
+esac
+
+# Build the native dynamic lib
+cd "$C_DIR" || { echo "C directory not found in $C_DIR"; exit 1; }
+if [ ! -f Makefile ]; then
+  echo "Makefile not found in $C_DIR"
+  exit 1
+fi
+make libusearch_c.$SHARED_LIB_EXT
+if [ $? -ne 0 ]; then
+  echo "Build failed!"
+  exit 1
+fi
+
+# Switch to the C# directory
+cd "$CSHARP_DIR" || { echo "C# directory not found in $CSHARP_DIR"; exit 1; }
+
+# Clean and build the .NET project
+dotnet clean
+dotnet build --configuration Release
+if [ $? -ne 0 ]; then
+  echo "Build failed!"
+  exit 1
+fi
+
+# Create a temporary path for dynamic library and move it there
+mkdir -p "$TEST_PROJ_RUNTIMES_TEMP_PATH"
+mv "$C_DIR/libusearch_c.$SHARED_LIB_EXT" "$TEST_PROJ_RUNTIMES_TEMP_PATH"
+
+# Run the tests
+dotnet test --configuration Release
+EXIT_CODE=$?
+
+# Clear the temporary path
+rm -rf "$TEST_PROJ_RUNTIMES_TEMP_PATH"
+
+if [ $EXIT_CODE -ne 0 ]; then
+  echo "Tests failed!"
+  exit 1
+else
+  echo "Tests passed successfully!"
+  exit 0
+fi
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..f190af89 100644
--- a/javascript/test.js
+++ b/javascript/test.js
@@ -1,30 +1,32 @@
 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');
+assert.equal(index.contains(15n), true, 'entry must be present after insertion');
 
-// 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..9ec0ea9c 100644
--- a/python/scripts/test.py
+++ b/python/scripts/test.py
@@ -5,6 +5,7 @@
 
 from usearch.io import load_matrix, save_matrix
 from usearch.eval import random_vectors
+from usearch.index import search
 
 from usearch.index import (
     Index,
@@ -33,7 +34,7 @@
     ScalarKind.F32,
     ScalarKind.F64,
     ScalarKind.F16,
-    ScalarKind.F8,
+    ScalarKind.I8,
 ]
 numpy_types = [np.float32, np.float64, np.float16]
 
@@ -81,6 +82,27 @@ def test_serializing_ibin_matrix(rows: int, cols: int):
     os.remove(temporary_filename + ".ibin")
 
 
+@pytest.mark.parametrize("rows", batch_sizes)
+@pytest.mark.parametrize("cols", dimensions)
+def test_exact_search(rows: int, cols: int):
+    """
+    Test exact search.
+
+    :param int rows: The number of rows in the matrix.
+    :param int cols: The number of columns in the matrix.
+    """
+    original = np.random.rand(rows, cols)
+    matches: BatchMatches = search(original, original, 10, exact=True)
+    top_matches = (
+        [int(m.keys[0]) for m in matches] if rows > 1 else int(matches.keys[0])
+    )
+    assert np.all(top_matches == np.arange(rows))
+
+    matches: Matches = search(original, original[0], 10, exact=True)
+    top_match = int(matches.keys[0])
+    assert top_match == 0
+
+
 @pytest.mark.parametrize("ndim", dimensions)
 @pytest.mark.parametrize("metric", continuous_metrics)
 @pytest.mark.parametrize("index_type", index_types)
@@ -108,21 +130,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 +161,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 +207,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 +304,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():