Problem solving · stretch-tier · asked at Amazon, Apple, Google, Meta, Microsoft, Netflix

Mutable Object as a Dict Key

mediumdebugging

Problem

A load balancer keeps the set of healthy backend endpoints. Endpoints are small objects with equality and hashing defined on (host, port) so they can live in a set:

class Endpoint:
    def __init__(self, host: str, port: int):
        self.host = host
        self.port = port

    def __eq__(self, other):
        return (self.host, self.port) == (other.host, other.port)

    def __hash__(self):
        return hash((self.host, self.port))


class Pool:
    def __init__(self):
        self.healthy: set[Endpoint] = set()

    def add(self, ep: Endpoint) -> None:
        self.healthy.add(ep)

    def mark_unhealthy(self, ep: Endpoint) -> None:
        self.healthy.discard(ep)

    def move_port(self, ep: Endpoint, new_port: int) -> None:
        # the sidecar restarted on a different port; keep the same object
        ep.port = new_port

Symptom: after a port move, mark_unhealthy(ep) does nothing, so traffic keeps flowing to a dead backend. Debug prints are contradictory: ep in pool.healthy is False, yet any(e == ep for e in pool.healthy) is True, and re-adding the endpoint grows len(pool.healthy) by one the first time (and is a no-op after that). Explain and fix.

Examples

  • ep = Endpoint("10.0.0.1", 8080); pool.add(ep); pool.mark_unhealthy(ep) → set is empty. Correct.
  • pool.add(ep); pool.move_port(ep, 9090); pool.mark_unhealthy(ep) → expected empty set. Actual: ep is still in the set (len == 1) and ep in pool.healthy is False.
  • After the above, pool.add(ep) → len == 2, and both members compare equal to each other. Iterating shows the "same" endpoint twice.

Hints

  1. A set does not scan its members with ==. What does it compute first, and when?
  2. __hash__ here depends on port. What happens to a stored hash when port changes after insertion?
  3. Why does Python raise TypeError: unhashable type: 'list' when you try to use a list as a key? The same reason applies to this class.

Last reviewed 8 September 2026