An HashTable is a simple but powerful data structure which is used to associate a key with a given value.
If you’re coming from PHP, you may remember associative arrays, which are implemented as HashTables!

$cars = array("Giulietta" => "Alfa Romeo", "Mustang" => "Ford", "Ghibli" => "Maserati");

As you can see in the PHP example above, we’re creating an array which contains key-value data. Nothing strange!
So what’s so special about associative arrays? Why are they so popular and powerful? How are they implemented?

Why HashTables?

HashTables are used to store data, just like arrays or objects.
An HashTable uses a hashing algorithm to compute an index into an array of buckets (or slots), where we can find the desired data.
Let’s illustrate an example:

HashTables

Given the example above, we can pretend we have the following associative array (in pseudocode):

myArr = [
  "Portofino" => "Ferrari",
  "Mustang"   => "Ford",
  "Carrera"   => "Porsche",
  "Ghibli"    => "Maserati",
  "Giulietta" => "Alfa Romeo"
]

So, if we want to get the “Ghibli" brand, we could loop over the array until we find the key “Ghibli” and then we retrive its value… but what if our array has thousands of items? Looping over it will require some time!

And what if we have some kind of hashing function that can retrieve for us the correct array index in a deterministic way?
That’s actually what HashTables do! So, our hashing function (in that case), could just return 4, which is the index of “Ghibli”! Much more efficient than looping over an array!

Does the previous illustration make sense now?

Are there any chances that we can get the same index for different keys?

Yes of course! That is called collision.
We get a collision every time that two (or more) hashed keys returns the same index.

How do we handle that?
We just store both the key-value pairs in the same index using other collections like arrays or linked lists.
This technique is called separate chaining.

Implementing an HashTable

First of all, we need a simple and efficient hashing algorithm:

function hash(key, size) {
  let hash = 0;

  for (let i = 0; i < key.length; i++) {
    let char = key[i];
    hash = (hash << 5) + char.charCodeAt(0);
    hash = (hash & hash) % size;
  }

  return hash;
}

Great! With this simple function we’ll be able to always get the same result for the same input. That’s exactly what we need!
Let’s quickly test it:

console.log(hash("ghibli",    42)); // => 39
console.log(hash("giulietta", 42)); // => 33
console.log(hash("portofino", 42)); // => 31

So now we need to implement our HashTable class!
Let’s write it down:

class HashTable {

  constructor() {
    this.size    = 42;
    this.buckets = [...Array(this.size)].map((_) => new Map());
  }
  
}

We just created our HashTable class with 42 buckets. A bucket is a single array index, which we’ll store our data in.
For instance, if we get 21 from our hashing function, that means that we’ll find our desired data in the 21st bucket.

We also use the newest ES6 Map object, which will iterate its element in insertion order and will save us a bit of work!

Let’s implement our insert method:

class HashTable {

  constructor() {
    this.size    = 42;
    this.buckets = [...Array(this.size)].map((_) => new Map());
  }

  insert(key, value) {
    const i = hash(key, this.size);
    this.buckets[i].set(key, value);
  }
  
}

Pretty easy, isn’t it? And what about a search method, to retrieve our data?

class HashTable {

  constructor() {
    this.size    = 42;
    this.buckets = [...Array(this.size)].map((_) => new Map());
  }

  insert(key, value) {
    const i = hash(key, this.size);
    this.buckets[i].set(key, value);
  }

  search(key) {
    const i = hash(key, this.size);
    return this.buckets[i].get(key);
  }
  
}

Even easier! Now we just need a method to delete our data:

class HashTable {

  constructor() {
    this.size    = 42;
    this.buckets = [...Array(this.size)].map((_) => new Map());
  }

  insert(key, value) {
    const i = hash(key, this.size);
    this.buckets[i].set(key, value);
  }

  search(key) {
    const i = hash(key, this.size);
    return this.buckets[i].get(key);
  }

  delete(key) {
    const i = hash(key, this.size);
    this.buckets[i].delete(key);
  }
  
}

Believe or not, we’re done! We’ve implemented a super basic HashTable in just a few lines of JavaScript! Let’s test it:

const H = new HashTable();

H.insert("Ghibli",    "Maserati");
H.insert("Portofino", "Ferrari");
H.insert("Carrera",   "Porsche");
H.insert("Mustang",   "Ford");
H.insert("Giulietta", "Alfa Romeo");
H.insert("Huracan",   "Lamborghini");
H.insert("Zonda",     "Pagani");
H.insert("124",       "Abarth");
H.insert("Chiron",    "Bugatti");
H.insert("DB11",      "Aston Martin");

console.log(H);

So now we log the following output to the console:

HashTable {
  size: 42,
  buckets: 
    [
      Map { '124' => 'Abart' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Zonda' => 'Pagani' },
      Map {},
      Map { 'Mustang' => 'Ford' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Ghibli' => 'Maserati' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Giulietta' => 'Alfa Romeo', 'DB11' => 'Aston Martin' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Chiron' => 'Bugatti' },
      Map { 'Portofino' => 'Ferrari' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Huracan' => 'Lamborghini' },
      Map {},
      Map {},
      Map { 'Carrera' => 'Porsche' },
      Map {},
      Map {},
    ]
}

As you can see, “Giulietta” and “DB11” causes a collision! Let’s try to retrieve their brands:

const GiuliettaBrand = H.search("Giulietta");
const DB11Brand      = H.search("DB11");

console.log({
  GiuliettaBrand,
  DB11Brand
})

/*
  {
    GiuliettaBrand: 'Alfa Romeo',
    DB11Brand: 'Aston Martin'
  }
*/

Oh yes! It works! And what about deleting some keys?

H.delete("Chiron");
H.delete("Mustang");
H.delete("Carrera");
H.delete("Zonda");

console.log(H);

/*
HashTable {
  size: 42,
  buckets: 
    [
      Map { '124' => 'Abart' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Ghibli' => 'Maserati' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Giulietta' => 'Alfa Romeo', 'DB11' => 'Aston Martin' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Portofino' => 'Ferrari' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map { 'Huracan' => 'Lamborghini' },
      Map {},
      Map {},
      Map {},
      Map {},
      Map {},
    ]
}
*/

A Little Disclaimer

Ok, we cheated a bit so far. We implemented an HashTable using the Map object, which is doing some work for us.
We’re not currently managing collisions by hand, but we’ve achieved our task: building a simple HashTable class and understood how it works!

Did you like this article? Consider becoming a Patron!

This article is CC0 1.0 (Public Domain) licensed.