Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

System Design (High-Level Design)  Module 14 – Search, geospatial and probabilistic data structures

Bloom and cuckoo filters: membership at scale

How Bloom filters answer "definitely not" or "maybe" in a few bits per item, how to size one, and when cuckoo filters, which can delete, fit better.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free intro, Pro: 5 of 20 minutes free
Free: How a Bloom filter says "no" and "maybe". With a pass: the other 8 sections and 3 interview answers, about 15 minutes.

What you will learn

  • Explain why Bloom filters give false positives but never false negatives
  • Size a filter for a target false-positive rate and predict what overfilling does
  • Choose a cuckoo filter or a counting Bloom filter when items must be deleted
  • Use filters in front of disks, crawlers and databases where a "no" saves real work

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain why Bloom filters give false positives but never false negatives
  • Size a filter for a target false-positive rate and predict what overfilling does
  • Choose a cuckoo filter or a counting Bloom filter when items must be deleted
  • Use filters in front of disks, crawlers and databases where a "no" saves real work

Before you start

On this page

Before a system pays for a disk read, a network call or a database query to look up one of a billion URLs, it can ask a cheaper question first: could this URL be in the set at all? A Bloom filter answers from memory, in about 1.2 GB for those billion URLs, far less than the URLs themselves. Its answer is either “definitely not”, which is always right, or “maybe”, which is wrong for about 1 in 100 URLs that are not in the set. Cuckoo filters answer the same question in similar space and can also delete items, which a plain Bloom filter cannot.

How a Bloom filter says “no” and “maybe”

A Bloom filter is an array of bits, all 0 at first, plus k hash functions that each map an item to one position in the array. Adding an item sets its k bits to 1. Checking an item reads the same k bits. If any of them is 0, the item was never added, because adding it would have set that bit. If all of them are 1, the item may have been added, or other items may have set those bits between them: a false positive (filter): A filter's "maybe" for an item that was never added, because other items have set every bit or slot the item checks. The share of such answers rises as the filter fills.. Two adds and two checks in a filter of 12 bits:

A 12-bit Bloom filter: apple and mango set five bits; kiwi meets a 0 bit and is rejected; olive meets only 1 bits.AddedThe filter: bits 0 to 11Checkedadd apple: sets bits 1, 5, 9add mango: sets bits 3, 5, 100: 01: 12: 03: 14: 05: 16: 07: 08: 09: 110: 111: 0check kiwi: bits 3, 7, 9bit 7 is 0, so nocheck olive: bits 1, 3, 10all are 1, so maybe:a false positiveset 1, 3, 5, 9, 10read the same positions

One 0 bit means no; all 1 bits only mean maybe

Text description of the diagram

The diagram has three rows, from top to bottom.

  1. Added: "add apple" sets bits 1, 5 and 9, and "add mango" sets bits 3, 5 and 10. Bit 5 is shared.
  2. The filter: 12 bits, numbered 0 to 11 and shown in two rows of six as "position: value"; bits 1, 3, 5, 9 and 10 are 1 and the rest are 0.
  3. Checked: "check kiwi" reads bits 3, 7 and 9; bit 7 is 0, so kiwi was certainly never added. "Check olive" reads bits 1, 3 and 10; all three are 1 although olive was never added, so the filter answers "maybe": a false positive.

The same mechanism at a larger size: 48 bits, 3 hash functions, 8 fruit words added and 20 checked. Step through how a word that was never added still answers “maybe”:

Interactive · step by step How olive answers maybe without being added Step through the adds that matter, then two checks. Each word's three bits are chosen by its hashes; a check reads the same three.
How olive answers maybe without being added, step 8 of 8. Showing banana, mango, cherry, olive, kiwi, bit 40 (1, set by banana), bit 25 (1, set by mango), bit 10 (1, set by cherry), bit 23. In focus: olive.readsreadsreadsreadsbananabits 46, 43, 40mangobits 25, 8, 39cherrybits 8, 9, 10olivebits 40, 25, 10kiwibits 23, 16, 9bit 401, set by bananabit 251, set by mangobit 101, set by cherrybit 230

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. 48 bits at 0. The filter starts as 48 bits, all 0, with 3 hash functions. Adding a word sets the 3 bits its hashes choose; checking a word reads the same 3 bits.
  2. Add banana. banana, the second word added, has bits 46, 43 and 40, and all three become 1. Bit 40 matters later.
  3. Add cherry. cherry, the third, sets bits 8, 9 and 10. Bit 10 matters later.
  4. Add mango. mango, the sixth, sets bits 25, 8 and 39. Bit 8 is already 1, because cherry and then grape set it, so three words now share that bit.
  5. All eight words in. With apple, grape, lemon, peach and plum added too, 21 of the 48 bits are 1. Every added word now answers maybe, because each of its bits is 1. Bit 23 is still 0.
  6. Check kiwi. kiwi's bits are 23, 16 and 9. Bit 23 is 0, and adding kiwi would have set it, so kiwi was certainly never added. The answer is no, and it is always right.
  7. Check olive. olive's bits are 40, 25 and 10, set by banana, mango and cherry between them. All three are 1, so the filter says maybe, although olive was never added.
  8. What the two checks show. A 0 bit proves a word was never added; all 1s prove nothing. Here 1 of the 12 words never added answered maybe, close to the 8.4 % that 21 set bits of 48 predict.

The program below is that filter and prints every check:

One program · 14 languages A Bloom filter of 48 bits with 8 words added O(k) per add or check, for k hash functions

Python 3.14 · main.py

M64 = 0xFFFFFFFFFFFFFFFF
BITS, HASHES = 48, 3
ADDED = "apple banana cherry grape lemon mango peach plum".split()
NEVER_ADDED = (
    "kiwi lime melon orange pear fig date guava papaya quince olive berry".split()
)


def hash64(text: str) -> int:
    # FNV-1a, then a mixing step so that similar words land far apart.
    h = 0xCBF29CE484222325
    for byte in text.encode():
        h = ((h ^ byte) * 0x100000001B3) & M64
    h = ((h ^ (h >> 30)) * 0xBF58476D1CE4E5B9) & M64
    h = ((h ^ (h >> 27)) * 0x94D049BB133111EB) & M64
    return h ^ (h >> 31)


def positions(word: str) -> list[int]:
    # k positions from one hash: h1 + i * h2 (double hashing).
    h = hash64(word)
    h1, h2 = h & 0xFFFFFFFF, (h >> 32) | 1
    return [(h1 + i * h2) % BITS for i in range(HASHES)]


bits = [0] * BITS
for word in ADDED:
    for p in positions(word):
        bits[p] = 1
print(
    f"{BITS} bits, {HASHES} hash functions, {len(ADDED)} words added, {sum(bits)} bits set"
)
for word in ADDED + NEVER_ADDED:
    ps = positions(word)
    zeros = [p for p in ps if bits[p] == 0]
    if zeros:
        answer = f"no (bit {zeros[0]} is 0)"
    else:
        answer = "maybe (it was added)" if word in ADDED else "maybe: a false positive"
    shown = ", ".join(f"{p:>2}" for p in ps)
    print(f"  {word:<7} bits {shown}   {answer}")

In Python: Python's integers never overflow, so every product is cut back to 64 bits with & M64, the wrap-around the other languages get from their 64-bit types.

Recorded with CPython 3.14.8 on macOS 26 arm64. It runs in your browser with Pyodide 314.0.7 (CPython 3.14.2). To run it yourself: python3 main.py

JavaScript Node 24 · main.mjs

const M64 = (1n << 64n) - 1n;
const BITS = 48;
const HASHES = 3;
const ADDED = "apple banana cherry grape lemon mango peach plum".split(" ");
const NEVER_ADDED = "kiwi lime melon orange pear fig date guava papaya quince olive berry".split(
  " ",
);

function hash64(text) {
  // FNV-1a, then a mixing step so that similar words land far apart.
  let h = 0xcbf29ce484222325n;
  for (let i = 0; i < text.length; i++) {
    h = ((h ^ BigInt(text.charCodeAt(i))) * 0x100000001b3n) & M64;
  }
  h = ((h ^ (h >> 30n)) * 0xbf58476d1ce4e5b9n) & M64;
  h = ((h ^ (h >> 27n)) * 0x94d049bb133111ebn) & M64;
  return h ^ (h >> 31n);
}

function positions(word) {
  // k positions from one hash: h1 + i * h2 (double hashing).
  const h = hash64(word);
  const [h1, h2] = [h & 0xffffffffn, (h >> 32n) | 1n];
  return Array.from({ length: HASHES }, (_, i) => Number((h1 + BigInt(i) * h2) % BigInt(BITS)));
}

const bits = new Array(BITS).fill(0);
for (const word of ADDED) {
  for (const p of positions(word)) bits[p] = 1;
}
const ones = bits.reduce((sum, b) => sum + b, 0);
console.log(
  `${BITS} bits, ${HASHES} hash functions, ${ADDED.length} words added, ${ones} bits set`,
);
for (const word of [...ADDED, ...NEVER_ADDED]) {
  const ps = positions(word);
  const zeros = ps.filter((p) => bits[p] === 0);
  let answer = ADDED.includes(word) ? "maybe (it was added)" : "maybe: a false positive";
  if (zeros.length > 0) answer = `no (bit ${zeros[0]} is 0)`;
  const shown = ps.map((p) => String(p).padStart(2)).join(", ");
  console.log(`  ${word.padEnd(7)} bits ${shown}   ${answer}`);
}

In JavaScript: A Number holds only 53 bits exactly, so the hash is a BigInt masked to 64 bits. The words are ASCII, so each charCodeAt is one UTF-8 byte.

Recorded with Node.js 24.21.0 LTS (V8 13.6) on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0). To run it yourself: node main.mjs

Java 25 LTS · Main.java

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.IntStream;

public class Main {
  static final int BITS = 48;
  static final int HASHES = 3;
  static final List<String> ADDED =
      List.of("apple banana cherry grape lemon mango peach plum".split(" "));
  static final List<String> NEVER_ADDED =
      List.of("kiwi lime melon orange pear fig date guava papaya quince olive berry".split(" "));

  static long hash64(String text) {
    // FNV-1a, then a mixing step so that similar words land far apart.
    long h = 0xCBF29CE484222325L;
    for (byte b : text.getBytes(StandardCharsets.UTF_8)) {
      h = (h ^ (b & 0xFF)) * 0x100000001B3L;
    }
    h = (h ^ (h >>> 30)) * 0xBF58476D1CE4E5B9L;
    h = (h ^ (h >>> 27)) * 0x94D049BB133111EBL;
    return h ^ (h >>> 31);
  }

  static int[] positions(String word) {
    // k positions from one hash: h1 + i * h2 (double hashing).
    long h = hash64(word);
    long h1 = h & 0xFFFFFFFFL;
    long h2 = (h >>> 32) | 1;
    return IntStream.range(0, HASHES).map(i -> (int) ((h1 + i * h2) % BITS)).toArray();
  }

  public static void main(String[] args) {
    Locale.setDefault(Locale.ROOT);
    int[] bits = new int[BITS];
    for (String word : ADDED) {
      for (int p : positions(word)) {
        bits[p] = 1;
      }
    }
    int ones = IntStream.of(bits).sum();
    System.out.println(
        BITS
            + " bits, "
            + HASHES
            + " hash functions, "
            + ADDED.size()
            + " words added, "
            + ones
            + " bits set");
    List<String> words = new ArrayList<>(ADDED);
    words.addAll(NEVER_ADDED);
    for (String word : words) {
      int[] ps = positions(word);
      int[] zeros = IntStream.of(ps).filter(p -> bits[p] == 0).toArray();
      String answer = ADDED.contains(word) ? "maybe (it was added)" : "maybe: a false positive";
      if (zeros.length > 0) {
        answer = "no (bit " + zeros[0] + " is 0)";
      }
      List<String> shown = IntStream.of(ps).mapToObj(p -> String.format("%2d", p)).toList();
      System.out.printf("  %-7s bits %s   %s%n", word, String.join(", ", shown), answer);
    }
  }
}

In Java: Java has no unsigned long, but multiplication wraps around modulo 2⁶⁴ all the same; >>> shifts in zeros where >> would copy the sign bit.

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. It runs in your browser with a TeaVM 0.16.0 build in QuickJS (quickjs-emscripten 0.32.0). To run it yourself: java Main.java

C++23 · main.cpp

#include <algorithm>
#include <cstdint>
#include <format>
#include <print>
#include <string>
#include <string_view>
#include <vector>

constexpr int BITS = 48;
constexpr int HASHES = 3;
constexpr std::string_view ADDED[] = {"apple", "banana", "cherry", "grape",
                                      "lemon", "mango",  "peach",  "plum"};
constexpr std::string_view NEVER_ADDED[] = {
    "kiwi", "lime",  "melon",  "orange", "pear",  "fig",
    "date", "guava", "papaya", "quince", "olive", "berry"};

std::uint64_t hash64(std::string_view text) {
  // FNV-1a, then a mixing step so that similar words land far apart.
  std::uint64_t h = 0xCBF29CE484222325ULL;
  for (const unsigned char byte : text) {
    h = (h ^ byte) * 0x100000001B3ULL;
  }
  h = (h ^ (h >> 30)) * 0xBF58476D1CE4E5B9ULL;
  h = (h ^ (h >> 27)) * 0x94D049BB133111EBULL;
  return h ^ (h >> 31);
}

std::vector<int> positions(std::string_view word) {
  // k positions from one hash: h1 + i * h2 (double hashing).
  const std::uint64_t h = hash64(word);
  const std::uint64_t h1 = h & 0xFFFFFFFFULL, h2 = (h >> 32) | 1;
  std::vector<int> out;
  for (std::uint64_t i = 0; i < HASHES; i++) {
    out.push_back(static_cast<int>((h1 + i * h2) % BITS));
  }
  return out;
}

void check(const std::vector<int> &bits, std::string_view word) {
  const auto ps = positions(word);
  const auto zero =
      std::ranges::find_if(ps, [&](int p) { return bits[p] == 0; });
  std::string answer = std::ranges::contains(ADDED, word)
                           ? "maybe (it was added)"
                           : "maybe: a false positive";
  if (zero != ps.end()) {
    answer = std::format("no (bit {} is 0)", *zero);
  }
  std::string shown;
  for (const int p : ps) {
    shown += std::format("{}{:>2}", shown.empty() ? "" : ", ", p);
  }
  std::println("  {:<7} bits {}   {}", word, shown, answer);
}

int main() {
  std::vector<int> bits(BITS, 0);
  for (const auto word : ADDED) {
    for (const int p : positions(word)) {
      bits[p] = 1;
    }
  }
  std::println("{} bits, {} hash functions, {} words added, {} bits set", BITS,
               HASHES, std::size(ADDED), std::ranges::count(bits, 1));
  for (const auto word : ADDED) {
    check(bits, word);
  }
  for (const auto word : NEVER_ADDED) {
    check(bits, word);
  }
}

In C++: std::uint64_t arithmetic wraps around modulo 2⁶⁴, exactly what FNV-1a needs, and std::ranges::contains, new in C++23, tells an added word from one never added.

Recorded with Apple clang 21.0.0 (C++23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.cpp -o main && ./main

C# 14 · .NET 10 · app.cs

using System.Text;

const int Bits = 48;
const int Hashes = 3;
var added = "apple banana cherry grape lemon mango peach plum".Split(' ');
var neverAdded = "kiwi lime melon orange pear fig date guava papaya quince olive berry".Split(' ');

var bits = new int[Bits];
foreach (var word in added)
{
    foreach (var p in Positions(word))
    {
        bits[p] = 1;
    }
}
Console.WriteLine(
    $"{Bits} bits, {Hashes} hash functions, {added.Length} words added, {bits.Sum()} bits set");
foreach (var word in added.Concat(neverAdded))
{
    var ps = Positions(word);
    var zeros = ps.Where(p => bits[p] == 0).ToArray();
    var answer = added.Contains(word) ? "maybe (it was added)" : "maybe: a false positive";
    if (zeros.Length > 0)
    {
        answer = $"no (bit {zeros[0]} is 0)";
    }
    var shown = string.Join(", ", ps.Select(p => $"{p,2}"));
    Console.WriteLine($"  {word,-7} bits {shown}   {answer}");
}

static ulong Hash64(string text)
{
    // FNV-1a, then a mixing step so that similar words land far apart.
    ulong h = 0xCBF29CE484222325;
    foreach (var b in Encoding.UTF8.GetBytes(text))
    {
        h = (h ^ b) * 0x100000001B3;
    }
    h = (h ^ (h >> 30)) * 0xBF58476D1CE4E5B9;
    h = (h ^ (h >> 27)) * 0x94D049BB133111EB;
    return h ^ (h >> 31);
}

static int[] Positions(string word)
{
    // k positions from one hash: h1 + i * h2 (double hashing).
    var h = Hash64(word);
    var (h1, h2) = (h & 0xFFFFFFFF, (h >> 32) | 1);
    return Enumerable.Range(0, Hashes).Select(i => (int)((h1 + (ulong)i * h2) % Bits)).ToArray();
}

In C#: ulong arithmetic wraps around in C#'s default unchecked context; Hash64 and Positions are static local functions below the top-level statements.

Recorded with C# 14 on .NET 10.0.12 (SDK 10.0.401) on macOS 26 arm64. It runs in your browser with the .NET runtime for WebAssembly 10.0.12. To run it yourself: dotnet run app.cs

C23 · main.c

#include <stdint.h>
#include <stdio.h>

enum { BITS = 48, HASHES = 3, ADDED = 8, WORDS = 20 };
static const char *const words[WORDS] = {
    "apple", "banana", "cherry", "grape",  "lemon",  "mango", "peach",
    "plum",  "kiwi",   "lime",   "melon",  "orange", "pear",  "fig",
    "date",  "guava",  "papaya", "quince", "olive",  "berry"};

static uint64_t hash64(const char *text) {
  // FNV-1a, then a mixing step so that similar words land far apart.
  uint64_t h = 0xCBF29CE484222325u;
  for (const unsigned char *p = (const unsigned char *)text; *p != '\0'; p++) {
    h = (h ^ *p) * 0x100000001B3u;
  }
  h = (h ^ (h >> 30)) * 0xBF58476D1CE4E5B9u;
  h = (h ^ (h >> 27)) * 0x94D049BB133111EBu;
  return h ^ (h >> 31);
}

static void positions(const char *word, int out[HASHES]) {
  // k positions from one hash: h1 + i * h2 (double hashing).
  const uint64_t h = hash64(word);
  const uint64_t h1 = h & 0xFFFFFFFFu, h2 = (h >> 32) | 1;
  for (int i = 0; i < HASHES; i++) {
    out[i] = (int)((h1 + (uint64_t)i * h2) % BITS);
  }
}

int main(void) {
  int bits[BITS] = {0};
  int ps[HASHES];
  int ones = 0;
  for (int w = 0; w < ADDED; w++) {
    positions(words[w], ps);
    for (int j = 0; j < HASHES; j++) {
      ones += !bits[ps[j]];
      bits[ps[j]] = 1;
    }
  }
  printf("%d bits, %d hash functions, %d words added, %d bits set\n", BITS,
         HASHES, ADDED, ones);
  for (int w = 0; w < WORDS; w++) {
    positions(words[w], ps);
    printf("  %-7s bits", words[w]);
    int zero = -1;
    for (int j = 0; j < HASHES; j++) {
      printf("%s%2d", j == 0 ? " " : ", ", ps[j]);
      if (zero < 0 && bits[ps[j]] == 0) {
        zero = ps[j];
      }
    }
    if (zero >= 0) {
      printf("   no (bit %d is 0)\n", zero);
    } else {
      printf("   %s\n",
             w < ADDED ? "maybe (it was added)" : "maybe: a false positive");
    }
  }
  return 0;
}

In C: The 20 words sit in one array with the 8 added words first, so a word's index says whether it was added; uint64_t wraps around modulo 2⁶⁴.

Recorded with Apple clang 21.0.0 (C23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang -std=c23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.c -o main && ./main

PHP 8.5 · main.php

<?php

declare(strict_types=1);

const BITS = 48;
const HASHES = 3;
const ADDED = ['apple', 'banana', 'cherry', 'grape', 'lemon', 'mango', 'peach', 'plum'];
const NEVER_ADDED = [
    'kiwi', 'lime', 'melon', 'orange', 'pear', 'fig',
    'date', 'guava', 'papaya', 'quince', 'olive', 'berry',
];

/** The low 64 bits of $a * $b, in 16-bit digits: an int product that overflows becomes a float. */
function mul64(int $a, int $b): int
{
    [$result, $carry] = [0, 0];
    for ($k = 0; $k < 4; $k++) {
        $sum = $carry;
        for ($i = 0; $i <= $k; $i++) {
            $sum += (($a >> (16 * $i)) & 0xFFFF) * (($b >> (16 * ($k - $i))) & 0xFFFF);
        }
        $result |= ($sum & 0xFFFF) << (16 * $k);
        $carry = $sum >> 16;
    }
    return $result;
}

/** A right shift that fills with zeros, where >> copies the sign bit. */
function shr(int $x, int $n): int
{
    return ($x >> $n) & (PHP_INT_MAX >> ($n - 1));
}

function hash64(string $text): int
{
    // FNV-1a, then a mixing step so that similar words land far apart.
    $h = (0xCBF29CE4 << 32) | 0x84222325;
    foreach (str_split($text) as $ch) {
        $h = mul64($h ^ ord($ch), 0x100000001B3);
    }
    $h = mul64($h ^ shr($h, 30), (0xBF58476D << 32) | 0x1CE4E5B9);
    $h = mul64($h ^ shr($h, 27), (0x94D049BB << 32) | 0x133111EB);
    return $h ^ shr($h, 31);
}

/** @return list<int> */
function positions(string $word): array
{
    // k positions from one hash: h1 + i * h2 (double hashing).
    $h = hash64($word);
    [$h1, $h2] = [$h & 0xFFFFFFFF, shr($h, 32) | 1];
    return array_map(fn(int $i): int => ($h1 + $i * $h2) % BITS, range(0, HASHES - 1));
}

$bits = array_fill(0, BITS, 0);
foreach (ADDED as $word) {
    foreach (positions($word) as $p) {
        $bits[$p] = 1;
    }
}
printf(
    "%d bits, %d hash functions, %d words added, %d bits set\n",
    BITS,
    HASHES,
    count(ADDED),
    array_sum($bits),
);
foreach ([...ADDED, ...NEVER_ADDED] as $word) {
    $ps = positions($word);
    $zeros = array_values(array_filter($ps, fn(int $p): bool => $bits[$p] === 0));
    $answer = in_array($word, ADDED, true) ? 'maybe (it was added)' : 'maybe: a false positive';
    if ($zeros !== []) {
        $answer = "no (bit {$zeros[0]} is 0)";
    }
    $shown = implode(', ', array_map(fn(int $p): string => sprintf('%2d', $p), $ps));
    printf("  %-7s bits %s   %s\n", $word, $shown, $answer);
}

In PHP: PHP turns an integer product past 2⁶³ into a float, so mul64 multiplies in 16-bit digits and keeps the low 64 bits, and shr shifts in zeros where >> copies the sign bit.

Recorded with PHP 8.5.11 (CLI) on macOS 26 arm64. To run it yourself: php main.php

Rust 1.99 · main.rs

const BITS: usize = 48;
const HASHES: u64 = 3;
const ADDED: &str = "apple banana cherry grape lemon mango peach plum";
const NEVER_ADDED: &str = "kiwi lime melon orange pear fig date guava papaya quince olive berry";

fn hash64(text: &str) -> u64 {
    // FNV-1a, then a mixing step so that similar words land far apart.
    let mut h: u64 = 0xCBF2_9CE4_8422_2325;
    for byte in text.bytes() {
        h = (h ^ u64::from(byte)).wrapping_mul(0x0100_0000_01B3);
    }
    h = (h ^ (h >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
    h = (h ^ (h >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
    h ^ (h >> 31)
}

fn positions(word: &str) -> Vec<usize> {
    // k positions from one hash: h1 + i * h2 (double hashing).
    let h = hash64(word);
    let (h1, h2) = (h & 0xFFFF_FFFF, (h >> 32) | 1);
    (0..HASHES)
        .map(|i| ((h1 + i * h2) % BITS as u64) as usize)
        .collect()
}

fn main() {
    let added: Vec<&str> = ADDED.split(' ').collect();
    let mut bits = [0u8; BITS];
    for word in &added {
        for p in positions(word) {
            bits[p] = 1;
        }
    }
    let ones = bits.iter().filter(|&&b| b == 1).count();
    let n = added.len();
    println!("{BITS} bits, {HASHES} hash functions, {n} words added, {ones} bits set");
    for word in ADDED.split(' ').chain(NEVER_ADDED.split(' ')) {
        let ps = positions(word);
        let answer = match ps.iter().find(|&&p| bits[p] == 0) {
            Some(zero) => format!("no (bit {zero} is 0)"),
            None if added.contains(&word) => "maybe (it was added)".to_string(),
            None => "maybe: a false positive".to_string(),
        };
        let shown: Vec<String> = ps.iter().map(|p| format!("{p:>2}")).collect();
        println!("  {word:<7} bits {}   {answer}", shown.join(", "));
    }
}

In Rust: wrapping_mul makes the 64-bit wrap-around explicit, where a plain * would stop a debug build with an overflow panic.

Recorded with rustc 1.99.0 (edition 2024) on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: rustc --edition 2024 -O main.rs && ./main

TypeScript 6.0 · main.ts

const M64 = (1n << 64n) - 1n;
const BITS = 48;
const HASHES = 3;
const ADDED = "apple banana cherry grape lemon mango peach plum".split(" ");
const NEVER_ADDED = "kiwi lime melon orange pear fig date guava papaya quince olive berry".split(
  " ",
);

function hash64(text: string): bigint {
  // FNV-1a, then a mixing step so that similar words land far apart.
  let h = 0xcbf29ce484222325n;
  for (let i = 0; i < text.length; i++) {
    h = ((h ^ BigInt(text.charCodeAt(i))) * 0x100000001b3n) & M64;
  }
  h = ((h ^ (h >> 30n)) * 0xbf58476d1ce4e5b9n) & M64;
  h = ((h ^ (h >> 27n)) * 0x94d049bb133111ebn) & M64;
  return h ^ (h >> 31n);
}

function positions(word: string): number[] {
  // k positions from one hash: h1 + i * h2 (double hashing).
  const h = hash64(word);
  const [h1, h2] = [h & 0xffffffffn, (h >> 32n) | 1n];
  return Array.from({ length: HASHES }, (_, i) => Number((h1 + BigInt(i) * h2) % BigInt(BITS)));
}

const bits: number[] = new Array<number>(BITS).fill(0);
for (const word of ADDED) {
  for (const p of positions(word)) bits[p] = 1;
}
const ones = bits.reduce((sum, b) => sum + b, 0);
console.log(
  `${BITS} bits, ${HASHES} hash functions, ${ADDED.length} words added, ${ones} bits set`,
);
for (const word of [...ADDED, ...NEVER_ADDED]) {
  const ps = positions(word);
  const zeros = ps.filter((p) => bits[p] === 0);
  let answer = ADDED.includes(word) ? "maybe (it was added)" : "maybe: a false positive";
  if (zeros.length > 0) answer = `no (bit ${zeros[0]} is 0)`;
  const shown = ps.map((p) => String(p).padStart(2)).join(", ");
  console.log(`  ${word.padEnd(7)} bits ${shown}   ${answer}`);
}

In TypeScript: The hash is a bigint, because a number holds only 53 bits exactly; bigint literals such as 0x100000001b3n need a target of ES2020 or later.

Recorded with TypeScript 6.0.3 (strict), run by Node.js 24.21.0 on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0) after Sucrase 3.35.1. To run it yourself: npx -p typescript@6.0.3 tsc --noEmit --strict main.ts && node main.ts

Go 1.27 · main.go

package main

import (
	"fmt"
	"slices"
	"strings"
)

const (
	bitCount  = 48
	hashCount = 3
)

var (
	added      = strings.Fields("apple banana cherry grape lemon mango peach plum")
	neverAdded = strings.Fields(
		"kiwi lime melon orange pear fig date guava papaya quince olive berry")
)

func hash64(text string) uint64 {
	// FNV-1a, then a mixing step so that similar words land far apart.
	h := uint64(0xCBF29CE484222325)
	for i := 0; i < len(text); i++ {
		h = (h ^ uint64(text[i])) * 0x100000001B3
	}
	h = (h ^ (h >> 30)) * 0xBF58476D1CE4E5B9
	h = (h ^ (h >> 27)) * 0x94D049BB133111EB
	return h ^ (h >> 31)
}

func positions(word string) []int {
	// k positions from one hash: h1 + i * h2 (double hashing).
	h := hash64(word)
	h1, h2 := h&0xFFFFFFFF, (h>>32)|1
	out := make([]int, hashCount)
	for i := range out {
		out[i] = int((h1 + uint64(i)*h2) % bitCount)
	}
	return out
}

func main() {
	bits := make([]int, bitCount)
	for _, word := range added {
		for _, p := range positions(word) {
			bits[p] = 1
		}
	}
	ones := 0
	for _, b := range bits {
		ones += b
	}
	fmt.Printf("%d bits, %d hash functions, %d words added, %d bits set\n",
		bitCount, hashCount, len(added), ones)
	for _, word := range slices.Concat(added, neverAdded) {
		ps := positions(word)
		answer := "maybe: a false positive"
		if slices.Contains(added, word) {
			answer = "maybe (it was added)"
		}
		if zero := slices.IndexFunc(ps, func(p int) bool { return bits[p] == 0 }); zero >= 0 {
			answer = fmt.Sprintf("no (bit %d is 0)", ps[zero])
		}
		shown := make([]string, len(ps))
		for i, p := range ps {
			shown[i] = fmt.Sprintf("%2d", p)
		}
		fmt.Printf("  %-7s bits %s   %s\n", word, strings.Join(shown, ", "), answer)
	}
}

In Go: uint64 arithmetic wraps around, and slices.IndexFunc returns -1 when every bit is 1, so the answer stays a maybe.

Recorded with Go 1.27.1 on macOS 26 arm64. It runs in your browser with the Go compiler in WebAssembly 1.27.1. To run it yourself: go run main.go

Swift 6.4 · main.swift

let bitCount = 48
let hashCount = 3
let added = "apple banana cherry grape lemon mango peach plum".split(separator: " ")
let neverAdded = "kiwi lime melon orange pear fig date guava papaya quince olive berry"
  .split(separator: " ")

func hash64(_ text: Substring) -> UInt64 {
  // FNV-1a, then a mixing step so that similar words land far apart.
  var h: UInt64 = 0xCBF2_9CE4_8422_2325
  for byte in text.utf8 {
    h = (h ^ UInt64(byte)) &* 0x100_0000_01B3
  }
  h = (h ^ (h >> 30)) &* 0xBF58_476D_1CE4_E5B9
  h = (h ^ (h >> 27)) &* 0x94D0_49BB_1331_11EB
  return h ^ (h >> 31)
}

func positions(_ word: Substring) -> [Int] {
  // k positions from one hash: h1 + i * h2 (double hashing).
  let h = hash64(word)
  let (h1, h2) = (h & 0xFFFF_FFFF, (h >> 32) | 1)
  return (0..<hashCount).map { Int((h1 + UInt64($0) * h2) % UInt64(bitCount)) }
}

func pad(_ text: String, _ width: Int, left: Bool = false) -> String {
  let fill = String(repeating: " ", count: max(0, width - text.count))
  return left ? fill + text : text + fill
}

var bits = [Int](repeating: 0, count: bitCount)
for word in added {
  for p in positions(word) { bits[p] = 1 }
}
let ones = bits.reduce(0, +)
print("\(bitCount) bits, \(hashCount) hash functions, \(added.count) words added, \(ones) bits set")
for word in added + neverAdded {
  let ps = positions(word)
  var answer = added.contains(word) ? "maybe (it was added)" : "maybe: a false positive"
  if let zero = ps.first(where: { bits[$0] == 0 }) {
    answer = "no (bit \(zero) is 0)"
  }
  let shown = ps.map { pad(String($0), 2, left: true) }.joined(separator: ", ")
  print("  \(pad(String(word), 7)) bits \(shown)   \(answer)")
}

In Swift: &* is the wrapping multiplication, where a plain * would stop the program on overflow. Without Foundation there is no String(format:), so pad lines up the columns.

Recorded with Apple Swift 6.4 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: swiftc -O main.swift -o main && ./main

Ruby 4.0 · main.rb

# frozen_string_literal: true

M64 = 0xFFFFFFFFFFFFFFFF
BITS = 48
HASHES = 3
ADDED = %w[apple banana cherry grape lemon mango peach plum].freeze
NEVER_ADDED = %w[kiwi lime melon orange pear fig date guava papaya quince olive berry].freeze

def hash64(text)
  # FNV-1a, then a mixing step so that similar words land far apart.
  h = 0xCBF29CE484222325
  text.each_byte { |byte| h = ((h ^ byte) * 0x100000001B3) & M64 }
  h = ((h ^ (h >> 30)) * 0xBF58476D1CE4E5B9) & M64
  h = ((h ^ (h >> 27)) * 0x94D049BB133111EB) & M64
  h ^ (h >> 31)
end

def positions(word)
  # k positions from one hash: h1 + i * h2 (double hashing).
  h = hash64(word)
  h1 = h & 0xFFFFFFFF
  h2 = (h >> 32) | 1
  Array.new(HASHES) { |i| (h1 + (i * h2)) % BITS }
end

bits = Array.new(BITS, 0)
ADDED.each { |word| positions(word).each { |p| bits[p] = 1 } }
puts "#{BITS} bits, #{HASHES} hash functions, #{ADDED.size} words added, #{bits.sum} bits set"
(ADDED + NEVER_ADDED).each do |word|
  ps = positions(word)
  zero = ps.find { |p| bits[p].zero? }
  answer = ADDED.include?(word) ? 'maybe (it was added)' : 'maybe: a false positive'
  answer = "no (bit #{zero} is 0)" if zero
  puts "  #{word.ljust(7)} bits #{ps.map { |p| p.to_s.rjust(2) }.join(', ')}   #{answer}"
end

In Ruby: Ruby's integers grow without limit, so every product is masked back to 64 bits; find returns nil when no bit is 0, and 0 itself is true in Ruby, so position 0 still counts as found.

Recorded with CRuby 4.0.7 on macOS 26 arm64. It runs in your browser with ruby.wasm 2.10.1 (Ruby 4.0.0). To run it yourself: ruby main.rb

Kotlin 2.4 · Main.kt

const val BITS = 48
const val HASHES = 3
val added = "apple banana cherry grape lemon mango peach plum".split(" ")
val neverAdded = "kiwi lime melon orange pear fig date guava papaya quince olive berry".split(" ")

fun hash64(text: String): ULong {
    // FNV-1a, then a mixing step so that similar words land far apart.
    var h = 0xCBF29CE484222325uL
    for (byte in text.encodeToByteArray()) {
        h = (h xor byte.toUByte().toULong()) * 0x100000001B3uL
    }
    h = (h xor (h shr 30)) * 0xBF58476D1CE4E5B9uL
    h = (h xor (h shr 27)) * 0x94D049BB133111EBuL
    return h xor (h shr 31)
}

fun positions(word: String): List<Int> {
    // k positions from one hash: h1 + i * h2 (double hashing).
    val h = hash64(word)
    val h1 = h and 0xFFFFFFFFuL
    val h2 = (h shr 32) or 1uL
    return List(HASHES) { i -> ((h1 + i.toULong() * h2) % BITS.toULong()).toInt() }
}

fun main() {
    val bits = IntArray(BITS)
    for (word in added) {
        for (p in positions(word)) bits[p] = 1
    }
    println("$BITS bits, $HASHES hash functions, ${added.size} words added, ${bits.sum()} bits set")
    for (word in added + neverAdded) {
        val ps = positions(word)
        val zero = ps.firstOrNull { bits[it] == 0 }
        var answer = if (word in added) "maybe (it was added)" else "maybe: a false positive"
        if (zero != null) answer = "no (bit $zero is 0)"
        val shown = ps.joinToString(", ") { it.toString().padStart(2) }
        println("  ${word.padEnd(7)} bits $shown   $answer")
    }
}

In Kotlin: ULong gives unsigned 64-bit arithmetic that wraps around, and its shr shifts in zeros; toUByte reads each byte of encodeToByteArray without its sign.

Recorded with Kotlin 2.4.20 on Java 25.0.4.1 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar

Dart 3.13 · main.dart

import 'dart:convert';

const bitCount = 48;
const hashCount = 3;
final added = 'apple banana cherry grape lemon mango peach plum'.split(' ');
final neverAdded =
    'kiwi lime melon orange pear fig date guava papaya quince olive berry'
        .split(' ');

int hash64(String text) {
  // FNV-1a, then a mixing step so that similar words land far apart.
  var h = 0xCBF29CE484222325;
  for (final byte in utf8.encode(text)) {
    h = (h ^ byte) * 0x100000001B3;
  }
  h = (h ^ (h >>> 30)) * 0xBF58476D1CE4E5B9;
  h = (h ^ (h >>> 27)) * 0x94D049BB133111EB;
  return h ^ (h >>> 31);
}

List<int> positions(String word) {
  // k positions from one hash: h1 + i * h2 (double hashing).
  final h = hash64(word);
  final (h1, h2) = (h & 0xFFFFFFFF, (h >>> 32) | 1);
  return [for (var i = 0; i < hashCount; i++) (h1 + i * h2) % bitCount];
}

void main() {
  final bits = List.filled(bitCount, 0);
  for (final word in added) {
    for (final p in positions(word)) {
      bits[p] = 1;
    }
  }
  final ones = bits.reduce((a, b) => a + b);
  print(
    '$bitCount bits, $hashCount hash functions, ${added.length} words added, $ones bits set',
  );
  for (final word in [...added, ...neverAdded]) {
    final ps = positions(word);
    final zero = ps.where((p) => bits[p] == 0).firstOrNull;
    var answer = added.contains(word)
        ? 'maybe (it was added)'
        : 'maybe: a false positive';
    if (zero != null) answer = 'no (bit $zero is 0)';
    final shown = ps.map((p) => '$p'.padLeft(2)).join(', ');
    print('  ${word.padRight(7)} bits $shown   $answer');
  }
}

In Dart: Dart's int has 64 bits on the VM and in WebAssembly and its products wrap around; >>> is the shift that fills with zeros.

Recorded with Dart 3.13.5 on macOS 26 arm64. It runs in your browser with a WebAssembly build by dart2wasm 3.13.5. To run it yourself: dart run main.dart

What every language printed

48 bits, 3 hash functions, 8 words added, 21 bits set
  apple   bits 33, 30, 27   maybe (it was added)
  banana  bits 46, 43, 40   maybe (it was added)
  cherry  bits  8,  9, 10   maybe (it was added)
  grape   bits  3,  8, 13   maybe (it was added)
  lemon   bits 13,  0, 35   maybe (it was added)
  mango   bits 25,  8, 39   maybe (it was added)
  peach   bits 45, 38, 31   maybe (it was added)
  plum    bits 36, 21,  6   maybe (it was added)
  kiwi    bits 23, 16,  9   no (bit 23 is 0)
  lime    bits 20,  9, 46   no (bit 20 is 0)
  melon   bits 45, 22, 47   no (bit 22 is 0)
  orange  bits  3, 20, 37   no (bit 20 is 0)
  pear    bits 14,  1, 36   no (bit 14 is 0)
  fig     bits 23,  6, 37   no (bit 23 is 0)
  date    bits 11,  2, 41   no (bit 11 is 0)
  guava   bits 19, 10,  1   no (bit 19 is 0)
  papaya  bits  3, 20, 37   no (bit 20 is 0)
  quince  bits 16, 13, 10   no (bit 16 is 0)
  olive   bits 40, 25, 10   maybe: a false positive
  berry   bits 36, 31, 26   no (bit 26 is 0)

Every added word answers “maybe”, and 11 of the 12 others answer “no” at their first 0 bit. Olive was never added, yet other words set all three of its bits: one false positive in 12, close to the 8.4 % that the share of set bits predicts, (21 ÷ 48)³. That share rises with every item added, so the error rate grows as the filter fills. Write the filter’s two methods yourself and run the sample tests:

Exercise · Easy · Python

Add to a Bloom filter and check membership

bloom.py holds a Bloom filter of m_bits bits with k hash functions. positions(item) gives an item's k bit positions, and bit(p) reads one bit. Write the two methods that make it a filter:

  • add(item) sets every one of the item's bits to 1. Bit p lives in byte p // 8 of self.bits, at position p % 8 inside that byte.
  • might_contain(item) returns False if any of the item's bits is 0, because the item was then certainly never added, and True if all of them are 1, because it may have been.

The tests check that added items are always found, that a bit left at 0 means no, and that a word nobody added can still answer True when other words happen to have set all its bits.

Starter code · bloom.py

M64 = 0xFFFFFFFFFFFFFFFF


def hash64(text):
    """FNV-1a over the UTF-8 bytes, then a mixing step."""
    h = 0xCBF29CE484222325
    for byte in text.encode():
        h = ((h ^ byte) * 0x100000001B3) & M64
    h = ((h ^ (h >> 30)) * 0xBF58476D1CE4E5B9) & M64
    h = ((h ^ (h >> 27)) * 0x94D049BB133111EB) & M64
    return h ^ (h >> 31)


class BloomFilter:
    def __init__(self, m_bits, k):
        self.m, self.k = m_bits, k
        self.bits = bytearray((m_bits + 7) // 8)  # bit p lives in byte p // 8, at position p % 8

    def positions(self, item):
        """The k bit positions of an item (double hashing)."""
        h = hash64(item)
        h1, h2 = h & 0xFFFFFFFF, (h >> 32) | 1
        return [(h1 + i * h2) % self.m for i in range(self.k)]

    def bit(self, p):
        return self.bits[p // 8] >> (p % 8) & 1

    def add(self, item):
        """Set the item's k bits."""
        # Replace this line with your code.
        pass

    def might_contain(self, item):
        """False only if the item was certainly never added."""
        # Replace this line with your code.
        return False
The sample tests · test_bloom.py
from bloom import BloomFilter

FRUIT = ["apple", "banana", "cherry", "grape", "lemon", "mango", "peach", "plum"]


def test_add_sets_the_items_bits():
    """adding one item sets exactly its k bits"""
    f = BloomFilter(48, 3)
    f.add("apple")
    assert [p for p in range(48) if f.bit(p)] == [27, 30, 33]


def test_added_items_are_found():
    """no false negatives: every added item answers True"""
    f = BloomFilter(10_000, 7)
    for i in range(500):
        f.add(f"user:{i}")
    assert all(f.might_contain(f"user:{i}") for i in range(500))


def test_empty_filter_contains_nothing():
    """a new filter answers False for anything"""
    f = BloomFilter(48, 3)
    assert not any(f.might_contain(w) for w in FRUIT)


def test_one_zero_bit_means_no():
    """an item with any of its bits still 0 was never added"""
    f = BloomFilter(48, 3)
    for w in FRUIT:
        f.add(w)
    assert not f.might_contain("kiwi")
    assert not f.might_contain("pear")


def test_false_positives_happen():
    """an item whose bits other items set answers True, although it was never added"""
    f = BloomFilter(48, 3)
    for w in FRUIT:
        f.add(w)
    assert f.might_contain("olive")
A hint

In add, loop over self.positions(item) and set each bit with self.bits[p // 8] |= 1 << (p % 8). In might_contain, return all(self.bit(p) == 1 for p in self.positions(item)).

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Two rules follow. A Bloom filter never answers “no” for an item that was added, so a system skips the expensive lookup on every “no” and still makes it on a “maybe”. And a plain filter cannot forget, because each of an item’s bits may also belong to other items. Relatives of the filter remove one limit each:

  • A counting Bloom filter keeps a small counter in place of each bit, so it can remove items, at several times the space.
  • A scalable Bloom filter adds a new, larger filter whenever the current one fills, so its error rate stays near the target.
  • Cuckoo filters keep a short fingerprint: A few bits taken from an item's hash and stored in place of the item, as a cuckoo filter stores them in the buckets of its table. of each item in one of two buckets of a hash table, so they can delete items and check one in two memory reads.

Key idea

A 0 bit proves an item was never added, while all 1s may come from other items: “no” skips the lookup and “maybe” still makes it.

Checkpoint

Check: Why "no" is certain and "maybe" is not

  1. Question 1 of 2 kiwi's bits are 23, 16 and 9, and bit 23 is still 0 after all eight words were added. What can the filter conclude?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: kiwi was never added, because adding it would have set bit 23

    Checking reads the same bits that adding sets. kiwi's bit 23 is 0, and adding kiwi would have set it, so kiwi was certainly never added: the filter answers no, and a no is always right.

  2. Question 2 of 2 olive was never added, yet its bits 40, 25 and 10 are all 1. A service checks the filter before reading its database. What does it do when olive is looked up?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It reads the database, because a maybe can be wrong and only the database knows

    A Bloom filter saves the expensive lookup only on a no. On a maybe the service still asks the source of truth, here the database, which knows that olive was never added.

Sizing a filter from its item count and error rate, with numbers Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Overfilling a filter: the failure reproduced
  • Deleting items: counting Bloom filters and cuckoo filters
  • Choosing between Bloom, counting and cuckoo filters, with numbers
  • Where filters sit in real systems, and where they do not
  • Practise: check yourself
  • Interview questions
    • A Bloom filter answers "maybe present" or "definitely not". Which answer can be wrong, and why?
    • A web crawler must skip URLs it has already fetched. It expects 1 billion URLs and accepts that 1 in 1,000 new URLs is skipped by mistake. Size the Bloom filter, and say what happens if the crawl reaches 3 billion URLs.
    • A Bloom filter in front of the user table answers "is this username taken?". People can delete their accounts and release their names, but the filter keeps saying "taken". What do you use instead, and what are its limits?
    • A key-value store keeps 2 billion keys in an LSM tree of 7 levels, and 90 % of lookups are for keys that do not exist. You can spend 3 GB of memory on per-file Bloom filters. How many bits per key do you give them, and how many disk reads does a lookup for a missing key cost?
  • Key takeaways

2 diagrams · 2 runnable examples (Python) · 3 interview answers · about 40 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Read a complete sample first: Design a unique ID generator

Terms in this lesson

counting Bloom filter
A Bloom filter with a small counter in place of each bit, so that items can be removed, at several times the space of a plain Bloom filter.
cuckoo filter
A filter that keeps a short fingerprint of each item in one of two buckets of a hash table, so that it can delete items and checks an item by reading two buckets.
false positive (filter)
A filter's "maybe" for an item that was never added, because other items have set every bit or slot the item checks. The share of such answers rises as the filter fills.
fingerprint
A few bits taken from an item's hash and stored in place of the item, as a cuckoo filter stores them in the buckets of its table.
scalable Bloom filter
A Bloom filter that adds a new, larger sub-filter whenever the current one fills, so that its false-positive rate stays near the target as the set grows.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.