PHP / Magento Dev Blog

  • Home

Hash Table – PHP implementation, O(1) lookup, deduplication and grouping

by Henryk Tews / Monday, 14 September 2026 / Published in Algorytmy

A hash table is a data structure with O(1) for insert, lookup and delete on average. PHP uses it internally for arrays – every PHP array is effectively a hash map. But knowing how it works under the hood lets you write more efficient code and understand why certain operations are fast and others surprisingly slow.

How a hash table works

A hash table stores key-value pairs. The key is transformed by a hash function into an index of the internal array (bucket). On collision (two keys produce the same index) one of two techniques is used: chaining (linked list in the bucket) or open addressing (searching for the next free slot). PHP uses chaining.

Key "sku"   -> hash() -> 42 -> bucket[42] -> "SKU-001"
Key "price" -> hash() -> 17 -> bucket[17] -> 99.99
Key "name"  -> hash() -> 42 -> bucket[42] -> collision! -> list: "SKU-001" -> "Widget"

PHP implementation

<?php

declare(strict_types=1);

class HashTable
{
    private array $buckets;
    private int   $size;
    private int   $count = 0;

    private const LOAD_FACTOR = 0.75;

    public function __construct(int $initialSize = 16)
    {
        $this->size    = $initialSize;
        $this->buckets = array_fill(0, $initialSize, null);
    }

    private function hash(string $key): int
    {
        // djb2 - simple but good hash function
        $hash = 5381;
        $len  = strlen($key);
        for ($i = 0; $i < $len; $i++) {
            $hash = (($hash << 5) + $hash) + ord($key[$i]);
            $hash &= 0x7FFFFFFF;
        }
        return $hash % $this->size;
    }

    public function set(string $key, mixed $value): void
    {
        if ($this->count / $this->size >= self::LOAD_FACTOR) {
            $this->resize();
        }

        $index   = $this->hash($key);
        $current = $this->buckets[$index];

        while ($current !== null) {
            if ($current['key'] === $key) {
                $current['value'] = $value;
                return;
            }
            $current = $current['next'];
        }

        $this->buckets[$index] = [
            'key'   => $key,
            'value' => $value,
            'next'  => $this->buckets[$index],
        ];
        $this->count++;
    }

    public function get(string $key): mixed
    {
        $index   = $this->hash($key);
        $current = $this->buckets[$index];

        while ($current !== null) {
            if ($current['key'] === $key) return $current['value'];
            $current = $current['next'];
        }

        return null;
    }

    public function delete(string $key): bool
    {
        $index = $this->hash($key);

        if ($this->buckets[$index] === null) return false;

        if ($this->buckets[$index]['key'] === $key) {
            $this->buckets[$index] = $this->buckets[$index]['next'];
            $this->count--;
            return true;
        }

        $prev = $this->buckets[$index];
        while ($prev['next'] !== null) {
            if ($prev['next']['key'] === $key) {
                $prev['next'] = $prev['next']['next'];
                $this->count--;
                return true;
            }
            $prev = $prev['next'];
        }

        return false;
    }

    private function resize(): void
    {
        $oldBuckets    = $this->buckets;
        $this->size   *= 2;
        $this->buckets = array_fill(0, $this->size, null);
        $this->count   = 0;

        foreach ($oldBuckets as $bucket) {
            while ($bucket !== null) {
                $this->set($bucket['key'], $bucket['value']);
                $bucket = $bucket['next'];
            }
        }
    }

    public function count(): int { return $this->count; }
}

Practical uses in PHP

<?php

// 1. Deduplication - O(n) instead of O(n²) via array_unique
function deduplicateSkus(array $skus): array
{
    $seen   = [];
    $result = [];
    foreach ($skus as $sku) {
        if (!isset($seen[$sku])) {  // isset on array = O(1) lookup
            $seen[$sku] = true;
            $result[]   = $sku;
        }
    }
    return $result;
}

// 2. Group products by category - O(n)
function groupByCategory(array $products): array
{
    $grouped = [];
    foreach ($products as $product) {
        $grouped[$product['category']][] = $product;
    }
    return $grouped;
}

// 3. Lookup table instead of loop - O(1) instead of O(n)
$statusLabels = [
    'pending'    => 'Pending payment',
    'processing' => 'Processing',
    'complete'   => 'Complete',
    'cancelled'  => 'Cancelled',
];

$label = $statusLabels[$orderStatus] ?? 'Unknown';  // O(1)

When PHP array (hash table) has worse O(1)

<?php

// PHP array has a fixed overhead per element: ~80 bytes (64-bit)
// 1,000,000 elements = ~80 MB just for the structure
// For very large sets - consider SplFixedArray (much less memory)

$fixed = new SplFixedArray(1000000);  // fixed-size array
// $fixed uses ~8 MB instead of ~80 MB for 1M ints

// array_key_exists vs isset - both O(1), but different result for null:
$a = ['key' => null];
isset($a['key']);             // false (null = does not exist for isset)
array_key_exists('key', $a); // true

Summary

Hash table is the foundation of PHP performance – every PHP array is an optimised hash map. Key lookup is O(1). Grouping, deduplication and building indexes through associative arrays are faster than loops with comparisons. Keep in mind the memory overhead with large datasets and the difference between isset and array_key_exists for null values.

About Henryk Tews

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}