В CPython у каждого объекта есть счетчик ссылок. Когда счетчик падает до нуля, объект можно сразу освободить. Это хорошо работает для обычных ацикличных графов объектов.
Проблема возникает, когда объекты ссылаются друг на друга: например, список a содержит ссылку на b, а b содержит ссылку на a. Если внешние переменные перестали указывать на этот цикл, счетчики ссылок внутри цикла все равно не равны нулю, потому что объекты держат друг друга. Reference counting сам по себе такой цикл не освободит.
Для этого в CPython есть cyclic garbage collector. Он периодически ищет группы контейнерных объектов, которые достижимы только друг из друга, но недостижимы из корней программы. Такие циклы можно собрать. При этом освобождение не обязано случиться ровно в момент зануления внешней ссылки.
Mutable default argument — другой классический runtime-подвох. Значение аргумента по умолчанию вычисляется один раз при создании функции, а не при каждом вызове. Поэтому функция вида def f(x=[]): x.append(1); return x будет переиспользовать один и тот же список во всех вызовах без явного x. Правильный паттерн — использовать None как sentinel и создавать новый список внутри функции.