Приходилось ли оптимизировать алгоритм, а не только архитектуру
Что проверяют
Отличаете ли вы «медленно, потому что архитектура» от «медленно, потому что алгоритм», умеете ли обосновать правку сложностью и подтвердить её замером. Ответ показывает, есть ли у вас привычка мерить, а не угадывать.
Как отвечать
Расскажите случай по схеме: чем померили, что увидели, что поменяли, что получилось. Инструменты в Python под рукой: cProfile с разбором через pstats, построчный профайлер для горячей функции, timeit для микрозамеров, tracemalloc по памяти, а для живого процесса снимок стека без остановки сервиса.
Дальше конкретика правок, которые и есть алгоритмическая часть:
- проверка вхождения в список это
O(n), вsetилиdictблизко кO(1). Вложенный цикл сравнения двух коллекций так превращается в один проход; - подсчёт частот это
collections.Counter, а верхние элементыheapq.nlargest, без полной сортировки ради нескольких значений; - удаление из начала списка это
O(n), для очереди берутdeque; - поиск по отсортированным данным через
bisect, повторные чистые вычисления черезfunctools.lru_cache.
Хорошо добавить, что в Python велика константа: перенос горячего цикла в векторные операции или в готовую библиотеку иногда даёт больше, чем смена асимптотики.
Где спотыкаются
Оптимизируют по интуиции. Без профиля переписывают участок, который занимает малую долю времени, а общий выигрыш ограничен именно этой долей, каким бы быстрым новый код ни стал.
Смотрят только на асимптотику и не считают память. Словарь на миллионы ключей и кеш без ограничения размера превращают выигрыш по времени в постоянный рост потребления, а на долгоживущем процессе это выглядит как утечка.
Меряют один раз и на игрушечных данных. Разброс между прогонами и прогрев дают разницу больше, чем сама правка, поэтому нужны повторы и реальный объём. Сюда же проверка крайних случаев: ускоренный код, который врёт на пустом входе, хуже медленного.