Python Dict vs List Lookup: Why Your Cache Gets Slow

Finding a key in a Python list means checking every item until you find it - O(n). A dict hashes the key and goes straight to it - O(1) on average. A cache built on a list of (key, value) tuples is fast with 10 entries and slow with 50,000. Here is how to change it to a dict without changing its behavior.

Start this project - freeLive cloud workspace in your browser · lands on your portfolio
AI Engineerpythonperformancehash-map

The slow cache

Here is an embedding cache that keeps its entries in a list:

class EmbeddingCache:
    def __init__(self) -> None:
        self.entries: list[tuple[str, list[float]]] = []

    def put(self, key: str, vector: list[float]) -> None:
        for i, (k, _) in enumerate(self.entries):
            if k == key:
                self.entries[i] = (key, vector)
                return
        self.entries.append((key, vector))

    def get(self, key: str) -> list[float] | None:
        for k, vec in self.entries:
            if k == key:
                return vec
        return None

    def __len__(self) -> int:
        return len(self.entries)

The code is correct. In a unit test with ten entries it is instant. The problem only appears with real data: get and put both walk the list from the start, so each call does work in proportion to the size of the cache.

Why the list gets slow

This is O(n) per call and O(n^2) to fill the cache. The cache was added to make repeat requests fast, but now each lookup costs more as the cache grows.

The dict version

A dict stores entries in a hash table. Python hashes the key, finds the slot, and checks only the few keys in that slot. Lookup, insert, and delete are O(1) on average, no matter how many entries there are:

class EmbeddingCache:
    def __init__(self) -> None:
        self.entries: dict[str, list[float]] = {}

    def put(self, key: str, vector: list[float]) -> None:
        self.entries[key] = vector          # insert or replace

    def get(self, key: str) -> list[float] | None:
        return self.entries.get(key)        # None if missing

    def __len__(self) -> int:
        return len(self.entries)

The behavior stays the same:

The class interface does not change, so no calling code needs to change.

Measure it

Do not trust the theory alone. Time both versions with the standard library:

import time
from cache import EmbeddingCache

c = EmbeddingCache()
start = time.perf_counter()
for i in range(10_000):
    c.put(f"prompt-{i}", [0.1, 0.2])
print(f"10k puts: {(time.perf_counter() - start) * 1000:.1f} ms")

start = time.perf_counter()
for i in range(1_000):
    c.get(f"prompt-{i * 7}")
print(f"1k gets:  {(time.perf_counter() - start) * 1000:.1f} ms")

With the list version the numbers grow fast as you raise the entry count. With the dict version they stay at a few milliseconds. Run the benchmark at the size you expect in production, not at the size of your test data.

The same bug in other places

The list-scan pattern shows up anywhere code looks something up by key:

A good rule: if the main thing you do with a collection is "find by key" or "is this in it?", use a dict or a set. Use a list when order is the main thing you need.

Things to know about dict keys

Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.

Start this project - free

What you'll practice

FAQ

Is a dict lookup faster than a list lookup in Python?

Yes, for finding by key. A dict uses a hash table, so a lookup is O(1) on average. Searching a list with a loop or the in operator is O(n), because Python checks items one by one.

Why is my Python cache slow with many entries?

A common cause is storing entries in a list and searching it on every get and put. The time per call grows with the size of the list. Store entries in a dict keyed on the cache key instead.

When should I use a list instead of a dict?

When order or position is what you need - a queue, a sequence of steps, items you sort or slice. When you mainly find things by key or check membership, use a dict or a set.

Does a dict keep the insertion order?

Yes. Since Python 3.7, dicts keep keys in the order they were first inserted, so replacing a list with a dict does not lose ordering.

Keep learning

Cache LLM Responses in RedisAI/ML projectCache a Hot Read Path With RedisAI/ML projectFix the N+1 QueryAI/ML projectAI/ML roadmapStep by step to hiredAI/ML interview questionsSTAR answersAll AI/ML projectsProjects hub

Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.

Start this project - free
Start this project - free