Never mind, just realised what was going on… Here’s solution anyway…
def __delitem__(self, key):
idx = self.get_valid_index(key)
print('deleting', self.data_list[idx], 'for key', key)
# del self.data_list[idx] # this `del` deletes the element itself not just it's value and males the self.data_list length to reduce, which makes the hash()%max_length not usable because it gives wrong IDX.
self.data_list[idx] = None # So now using TOMBSTONE approach and just making those entries None, instead of deleting them
I have used this method as well but unfortunately after removing an item it seems it’s impossible to find any other ones that come after it that’s because in the get_valid_index method whenever kv is None we stop searching and just return the index (that is when more than one item has the same index).