You now have all the pieces. A bit array stores the state. Hash functions map each item to a set of positions. Putting them together gives you a Bloom filter.
To add an item to the filter:
1 in the bit array.add(item: string): number[] {
const positions = this.getPositions(item);
for (const pos of positions) {
this.bits[pos] = true;
}
return positions;
}That is the entire insert operation. You never store the item itself — only its footprint in the bit array.
To check whether an item might be in the filter:
1, the item might be present.0, the item is definitely not present.test(item: string): { result: boolean; positions: number[] } {
const positions = this.getPositions(item);
const result = positions.every((pos) => this.bits[pos]);
return { result, positions };
}The key insight: you can only get a 0 at a position if nothing has ever set it. Since insert always sets bits and never clears them, a 0 is a guaranteed miss.
Insert a few words, then test a word you inserted — it should always return “possibly present”. Test a word you never inserted — most of the time it will return “definitely not present”, but occasionally the bits happen to match and you will see a false positive.
One important limitation: you cannot remove an item from a Bloom filter.
When you insert an item, you flip some bits to 1. But those bits might be shared with other items. If you clear them to remove one item, you would also break the membership test for every other item that shares those bits.
There are variants of the Bloom filter (like the Counting Bloom filter) that support deletion, but they use more memory to do it.