Data Structures: Hash Tables
By: Jomarie Polanco
Tags: JavaScript, Hash Table, Coding, Data Structures, Coding Interviews

Every language calls them something different. In Python, we’re talking about dictionaries. Ruby, it’s hashes. JavaScript, we’re talking objects. In Java, we’re talking about maps. These are all, fundamentally, a language’s built-in implementation of a hash table.

A hash table is a data structure that is used to store key-value pairs. Just like anything in life, when something seems so simple, there is probably a lot of discipline and hard work behind it. Hash tables are no different. They are actually a technique used to change a range of key values into a range of indices of an array. (That’s another data structure we’re all very familiar with). Hashes are used for many reasons, including:

protecting data

storing data

user authentication

cryptography (cryptographic hashes are a whole other story that I have yet to be brave enough to look into…we’ll save that for another time)

cryptocurrency

Unlike arrays, hash tables are not ordered, and because of that, hash tables are much faster than arrays at finding values, adding new values, and removing values (O(1) vs O(n)). (**It’s important to note that though hash tables are fast in these operations, they perform poorly when it comes to searching, like finding the minimum and maximum values in a data set. Here, a binary search tree would be more appropriate.) A hash table uses a hash function to determine an index for each key-value pair where the pair is then stored in an array of “slots” where the desired value can be found.

An example of an Object (JavaScript Hash Table implementation).

What makes a good hash?

There are 3 key concepts when it comes to implementing the use of a hash table.

The hash must be fast. Hashes should have a Big O of O(1), or constant time. This means that as the hash increases in size, it does not take any longer to access a key-value pair. The time it takes to access any pair in the hash is constant. The hash doesn’t cluster outputs at a specific index, but instead distributes each key-value pair uniformly in its slots. The hash is deterministic, meaning the same key yields the same index every time.

Implementing a Hash Function

Let’s get to the good stuff. The job of the hash function is to take a key and convert it into a number, which will be the index at which the key-value pair will be stored. In JavaScript, keys are inherently strings, so a functional hash function would convert strings to integers. I used charCodeAt() function to get the UTF-16 code point for each character in the string. I’m going to show you my thought process to getting to the end implementation.

Here are three implementations (SPOILER: they’re bad) for a hash function. The first function is the best out of all three, but it’s slow. This function would have to loop through a ton of times (O(n)) based on the number of slots available for the key. The second function is just a cluster. Every single key is going to be given an index of 0. The last function is non-deterministic. It’s appointing a random number every time the key goes through. This means that you won’t always get the same index for the same key.

A functional, and better approach, to a hash function would also use the UTF-16 code print of each character in a string, but would also multiply the value by a prime number and use the remainder of dividing the total.

…what?

Prime Numbers and Why They Work

Yes, you read that right. Below, I used the number 31, but any prime number would do. The most optimal are known to be 31, 33, 37 and 41. The reason why is pretty complex, but here we go:

Prime numbers are un-divisible by any other numbers. Hashing works by issuing a value (of a key-value pair) into one of the many slots a hash has for fast retrieval later on. The hashes ability to identify which slot it needs to store the value in is important since hashes retrieve information (or should retrieve info) in constant time, O(1). The easiest way to retrieve information in constant time is to figure out a way to generate unique numbers from key-value pairs. Prime numbers are unique and, therefore, the product of a prime number with any other number has the best chance at being unique because a prime is used to create it. Researchers have found that using a prime number gives a better distribution to the key-value pairs and a lesser number of collisions.

Another thing to note here is using modulo arithmetic. This is common in hash functions since the result of a hash function must fit within the range of slots. Using the remainder of the total divided by the number of slots ensures that the output of the hash function (and therefore the placement of the values) will be in the correct range.

Collisions

I’ve mentioned collisions already, but they are an important part of hash tables. Even with an efficient hash function, it isn’t impossible for two keys to output the same value. The hash function above is a good example of that. A strategy for handling collisions should be added to the implementation. Functions can be created that will take the value from the hash function and stores the value in that position AFTER checking that there is no collision. We can use separate chaining (make each cell of a hash table point to a linked list of records that have the same hash function value) or linear probing (probing slots until an empty one is found).

With separate chaining, we store values using a more sophisticated data structure like a linked list. This lets us store multiple key-value pairs at the same index. With linear probing, no key-value pair is stored at the same index. We search through the array of slots to find the next empty slot if the index for the key has already been taken.

Implementation

Here is a solid implementation of a hash table. You’ll see our hash function from above in there. I also have a set and get function to help with collisions. The set function stores the key-value pair via separate chaining using a nested array, and the get function retrieves the value from the key or returns undefined if the key is not found.

Circling back to what makes a good hash, this implementation ensures all three factors. First, our hash retrieves, adds, and removes data in constant time. Second, via separate chaining, we are reducing the number of collisions. Lastly, the allocation of indices is deterministic. (**Edit: This is not the best implementation. User, octree, has some great tips in the comments to improve this. I am still learning and appreciate any more tips!)

Out of all the data structures, the hash table (and maybe arrays too) are so widely used that most programming languages have built-in implementations of them. But understanding how the data structure works under the hood puts you one step closer to writing fast, efficient code. Hash tables aren’t always the answer for your project, but they are a great stepping stone and for most projects, get the job done!