Quick answer: Sprite.kill() removes from Groups but Python keeps it alive while any reference exists. Audit with gc.get_referrers(); switch peripheral references to weakref.ref().

A bullet-hell shooter spawns thousands of enemies per minute. Memory profile shows constant growth despite calling kill() on dead enemies. After 5 minutes, the game stutters from RAM pressure.

Why kill() Doesn't Free

class Enemy(pygame.sprite.Sprite):
    def die(self):
        self.kill()   # removes from all groups

# but elsewhere:
all_enemies = []   # list of enemies for boss targeting
all_enemies.append(enemy)
# after .kill(), still in all_enemies — alive

Lists, dicts, and any attribute holding the sprite keeps it alive in CPython’s refcount.

Audit with gc.get_referrers

import gc

def audit_sprite(sprite):
    refs = gc.get_referrers(sprite)
    for r in refs:
        print(type(r), id(r))

Run after kill(). Lists what holds the sprite. Common: own enemy list, AI target attributes, particle parent refs.

Fix 1: Explicit Cleanup

def on_enemy_died(self, enemy):
    enemy.kill()
    self.all_enemies.remove(enemy)
    for ally in self.allies:
        if ally.target is enemy:
            ally.target = None

Explicit but error-prone — easy to miss a reference site.

Fix 2: Weakrefs for Peripheral Refs

import weakref

class Ally(pygame.sprite.Sprite):
    def set_target(self, enemy):
        self.target_ref = weakref.ref(enemy)

    def update(self):
        target = self.target_ref() if self.target_ref else None
        if target and target.alive():
            self.aim_at(target)

Weakrefs don’t prevent GC. When the enemy dies and is removed from groups, the weakref returns None.

Verifying

Track len(gc.get_objects()) over time. Should stabilize after warmup. Spawn-then-kill 10,000 enemies; memory returns to baseline within seconds of GC running.

“kill() unhooks from Groups; you still own all the references you took. Weakref the optional ones.”

For shmups especially, the spawn/kill cycle is constant — small leaks compound rapidly. weakref discipline saves hours of profiling later.