В множество (set) можно помещать только hashable (хешируемые) объекты.
Требования
🔹 объект имеет
🔹 объект неизменяемый (immutable)
🔹 корректно реализует
Можно
🔹
🔹
🔹
Нельзя
🔹
🔹
🔹
(они изменяемые → нет стабильного хеша)
🐍Вопросы с собесов -> ProstoPython
Требования
🔹 объект имеет
__hash__()🔹 объект неизменяемый (immutable)
🔹 корректно реализует
__eq__()Можно
🔹
int, float, str, bool🔹
tuple (если внутри тоже hashable)🔹
frozensetНельзя
🔹
list🔹
dict🔹
set(они изменяемые → нет стабильного хеша)
🐍Вопросы с собесов -> ProstoPython
🔥4
🧰 Code Cleanup
На первый взгляд — аккуратный код.
Но в нём есть дублирование, которое замедляет чтение.
❌ Плохой код
✅ Улучшенный код
💥 Объяснение
В первом варианте происходит лишнее:
👉 Ты сначала проверяешь наличие ключа
👉 Потом сразу используешь его же
Это дублирование логики.
🐍Вопросы с собесов -> ProstoPython
На первый взгляд — аккуратный код.
Но в нём есть дублирование, которое замедляет чтение.
❌ Плохой код
def get_active_users(users):
result = []
for user in users:
if "is_active" in user:
if user["is_active"]:
result.append(user["name"])
return result
✅ Улучшенный код
def get_active_users(users):
return [user["name"] for user in users if user.get("is_active")]
💥 Объяснение
В первом варианте происходит лишнее:
👉 Ты сначала проверяешь наличие ключа
👉 Потом сразу используешь его же
Это дублирование логики.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4
🧠 Interview Thinking
Классическая задача.
Но валятся здесь не из-за кода — а из-за мышления.
📌 Задача
Дана строка:
Нужно вернуть индекс первого уникального символа.
Если нет — вернуть
👶 Как думает junior
На первый взгляд — логично.
👉 Проверяем каждый символ
👉 Если встречается 1 раз — возвращаем индекс
💥 Проблема
Метод
А он вызывается внутри цикла.
👉 Итог: O(n²)
На маленьких строках — ок
На больших — просадка
🧠 Как думает сильный кандидат
🚀 Что изменилось
👉 Сначала считаем частоты → O(n)
👉 Потом один проход → O(n)
Итого: O(n)
⚠️ Но дело не только в оптимизации
Сильный кандидат:
🔷 сразу проговаривает сложность
🔷 замечает повторные операции
🔷ищет способ вынести их из цикла
🎯 Что хочет интервьюер
Не просто решение.
А чтобы ты сказал:
«Здесь есть повторный подсчёт.
Я могу вынести его в отдельную структуру»
🐍Вопросы с собесов -> ProstoPython
Классическая задача.
Но валятся здесь не из-за кода — а из-за мышления.
📌 Задача
Дана строка:
s = "leetcode"
Нужно вернуть индекс первого уникального символа.
Если нет — вернуть
-1.👶 Как думает junior
def first_unique(s):
for i in range(len(s)):
if s.count(s[i]) == 1:
return i
return -1
На первый взгляд — логично.
👉 Проверяем каждый символ
👉 Если встречается 1 раз — возвращаем индекс
💥 Проблема
Метод
count() — это O(n)А он вызывается внутри цикла.
👉 Итог: O(n²)
На маленьких строках — ок
На больших — просадка
🧠 Как думает сильный кандидат
from collections import Counter
def first_unique(s):
freq = Counter(s)
for i, char in enumerate(s):
if freq[char] == 1:
return i
return -1
🚀 Что изменилось
👉 Сначала считаем частоты → O(n)
👉 Потом один проход → O(n)
Итого: O(n)
⚠️ Но дело не только в оптимизации
Сильный кандидат:
🔷 сразу проговаривает сложность
🔷 замечает повторные операции
🔷ищет способ вынести их из цикла
🎯 Что хочет интервьюер
Не просто решение.
А чтобы ты сказал:
«Здесь есть повторный подсчёт.
Я могу вынести его в отдельную структуру»
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥3
📈 From O(n²) to O(n)
Задача простая.
Но именно здесь многие застревают в brute force.
📌 Задача
Дан массив чисел и число
Нужно найти два индекса, сумма элементов по которым равна
Ответ:
❌ Наивное решение (brute force)
💥 Проблема
Два вложенных цикла:
👉 O(n²)
На маленьких массивах — нормально
На больших — боль
🧠 Ключевой вопрос
На первый взгляд кажется:
«Ну а как иначе? Надо же проверить все пары»
Но…
👉 А если мы будем помнить, что уже видели?
✅ Оптимизированное решение
🚀 Что произошло
Мы заменили:
👉 «перебирать все пары»
на
👉 «проверять, встречали ли нужное значение раньше»
📉 Сложность
Один проход по массиву → O(n)
Поиск в словаре → O(1)
Итого: O(n)
⚠️ Где часто ошибаются
❌ Сначала добавляют в
→ ловят кейс, где элемент используется дважды
Правильно:
👉 сначала проверка
👉 потом добавление
🐍Вопросы с собесов -> ProstoPython
Задача простая.
Но именно здесь многие застревают в brute force.
📌 Задача
Дан массив чисел и число
target.Нужно найти два индекса, сумма элементов по которым равна
target.nums = [2, 7, 11, 15]
target = 9
Ответ:
[0, 1]❌ Наивное решение (brute force)
def two_sum(nums, target):
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
💥 Проблема
Два вложенных цикла:
👉 O(n²)
На маленьких массивах — нормально
На больших — боль
🧠 Ключевой вопрос
На первый взгляд кажется:
«Ну а как иначе? Надо же проверить все пары»
Но…
👉 А если мы будем помнить, что уже видели?
✅ Оптимизированное решение
def two_sum(nums, target):
seen = {}
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
🚀 Что произошло
Мы заменили:
👉 «перебирать все пары»
на
👉 «проверять, встречали ли нужное значение раньше»
📉 Сложность
Один проход по массиву → O(n)
Поиск в словаре → O(1)
Итого: O(n)
⚠️ Где часто ошибаются
❌ Сначала добавляют в
seen, потом проверяют→ ловят кейс, где элемент используется дважды
Правильно:
👉 сначала проверка
👉 потом добавление
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍3
Попробуйте ответить без запуска, что выведет код, разбор будет через 2 часа
🐍Вопросы с собесов -> ProstoPython
🐍Вопросы с собесов -> ProstoPython
👍4
Что выведет код выше?
Anonymous Poll
18%
[1, 2, 3] [1, 2, 3, 4]
73%
[1, 2, 3, 4] [1, 2, 3, 4]
9%
[1, 2, 3] [4]
0%
Ошибка
✅ Правильный ответ:
[1, 2, 3, 4]
[1, 2, 3, 4]
💥 Разбор
На первый взгляд кажется:
data += [4] создаёт новый список
Но это не так.
🧠 Что происходит на самом деле
Для списков:
data += [4]
👉 это мутация (аналог extend)
👉 объект изменяется на месте
А data и lst — это одна и та же ссылка.
⚠️ Где ловушка
Многие думают:
+= всегда создаёт новый объект
Но в Python:
для list → мутация
для tuple, str → новый объект
Один и тот же оператор — разное поведение.
🐍Вопросы с собесов -> ProstoPython
[1, 2, 3, 4]
[1, 2, 3, 4]
💥 Разбор
На первый взгляд кажется:
data += [4] создаёт новый список
Но это не так.
🧠 Что происходит на самом деле
Для списков:
data += [4]
👉 это мутация (аналог extend)
👉 объект изменяется на месте
А data и lst — это одна и та же ссылка.
⚠️ Где ловушка
Многие думают:
+= всегда создаёт новый объект
Но в Python:
для list → мутация
для tuple, str → новый объект
Один и тот же оператор — разное поведение.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍3
🧰 Code Cleanup
На первый взгляд — нормальный код.
Но в нём скрытая неэффективность и шум.
❌ Плохой код
✅ Улучшенный код
💥 Объяснение
В первом варианте:
👉 дважды обращаешься к
👉 руками обрабатываешь инициализацию
Код работает, но перегружен лишними действиями.
🧠 Что здесь неочевидно
Многие думают:
«Ну это же стандартный паттерн»
На самом деле:
ты берёшь на себя работу, которую уже решил Python
⚠️ Где часто ошибаются
Пишут так «по привычке»:
Хотя это почти всегда можно упростить.
⚡️ Маленькое правило
👉 Если собираешь значения в группы → смотри в сторону
👉 Убирай повторные обращения к одним и тем же данным
🐍Вопросы с собесов -> ProstoPython
На первый взгляд — нормальный код.
Но в нём скрытая неэффективность и шум.
❌ Плохой код
def group_by_type(items):
result = {}
for item in items:
if item["type"] not in result:
result[item["type"]] = []
result[item["type"]].append(item)
return result
✅ Улучшенный код
from collections import defaultdict
def group_by_type(items):
result = defaultdict(list)
for item in items:
result[item["type"]].append(item)
return result
💥 Объяснение
В первом варианте:
👉 дважды обращаешься к
item["type"]👉 руками обрабатываешь инициализацию
Код работает, но перегружен лишними действиями.
🧠 Что здесь неочевидно
Многие думают:
«Ну это же стандартный паттерн»
На самом деле:
ты берёшь на себя работу, которую уже решил Python
⚠️ Где часто ошибаются
Пишут так «по привычке»:
if key not in dict:
dict[key] = []
Хотя это почти всегда можно упростить.
⚡️ Маленькое правило
👉 Если собираешь значения в группы → смотри в сторону
defaultdict👉 Убирай повторные обращения к одним и тем же данным
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
⏱️ Big O Breakdown
Какая сложность?
A) O(n)
B) O(n²)
C) O(n · m)
D) Зависит от формы матрицы
Правильный ответ:A — O(n)
Разбор
Глаз видит два вложенных цикла и автоматически кричит «O(n²)!». Это рефлекс, и он часто врёт.
Сложность считается не по количеству циклов, а по числу итераций относительно размера входа.
Здесь внутренний пробегает по элементам строки. А внешний — по строкам. В сумме мы касаемся каждого элемента ровно один раз.
Если всего элементов — мы делаем шагов. Это O(n).
🐍Вопросы с собесов -> ProstoPython
def process(matrix):
result = []
for row in matrix:
for val in row:
result.append(val)
return result
matrix — это список из n элементов суммарно (например, 100 чисел, разбитых на строки разной длины).Какая сложность?
A) O(n)
B) O(n²)
C) O(n · m)
D) Зависит от формы матрицы
Правильный ответ:
Разбор
Сложность считается не по количеству циклов, а по числу итераций относительно размера входа.
Здесь внутренний
for val in rowЕсли всего элементов
nn🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4
📈 From O(n·k) to O(n)
Задача
Дан массив и число
Наивное решение — O(n·k)
Чисто, читаемо, проходит на маленьких данных.
Проблема: для каждого из
При
Оптимизация — O(n) через deque
Идея: хранить в очереди индексы кандидатов на максимум. Поддерживать в ней убывающий порядок значений.
Почему это O(n), а не O(n·k)?
На первый взгляд внутри
На самом деле каждый элемент попадает в очередь и выпадает из неё ровно один раз за всю работу алгоритма. Сумма всех итераций внутренних
Это называется амортизированный анализ: смотрим не на худший шаг, а на суммарную работу.
🐍Вопросы с собесов -> ProstoPython
Задача
Дан массив и число
k. Для каждого окна длины k найти максимум.nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
→ [3, 3, 5, 5, 6, 7]
Наивное решение — O(n·k)
def max_sliding(nums, k):
return [max(nums[i:i+k]) for i in range(len(nums) - k + 1)]
Чисто, читаемо, проходит на маленьких данных.
Проблема: для каждого из
n окон вызываем max(), который проходит по k элементам. Итого — O(n·k).При
n = 10⁶ и k = 10⁵ это уже 10¹¹ операций. Тесты упадут по таймауту.Оптимизация — O(n) через deque
Идея: хранить в очереди индексы кандидатов на максимум. Поддерживать в ней убывающий порядок значений.
from collections import deque
def max_sliding(nums, k):
dq = deque() # индексы, значения по ним убывают
result = []
for i, n in enumerate(nums):
# выбрасываем индексы, вышедшие за окно
while dq and dq[0] <= i - k:
dq.popleft()
# выбрасываем всё, что меньше текущего — они никогда не станут максимумом
while dq and nums[dq[-1]] < n:
dq.pop()
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]])
return result
Почему это O(n), а не O(n·k)?
На первый взгляд внутри
for есть два while — кажется, что снова вложенность.На самом деле каждый элемент попадает в очередь и выпадает из неё ровно один раз за всю работу алгоритма. Сумма всех итераций внутренних
while — не больше 2n.Это называется амортизированный анализ: смотрим не на худший шаг, а на суммарную работу.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥3
Новая рубрика🔥🔥🔥:
⚖️ This vs That — «Это или то»
Сравнение похожих штук, между которыми все путаются
Мы стараемся для вас 💛
🔥 — если ждали новую рубрику
🐍Вопросы с собесов -> ProstoPython
⚖️ This vs That — «Это или то»
Сравнение похожих штук, между которыми все путаются
Мы стараемся для вас 💛
🔥 — если ждали новую рубрику
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥3
⚖️ This vs That:
Оба — методы без
staticmethod — функция, которая просто живёт внутри класса
Не получает ни
Если убрать
classmethod — метод, который знает свой класс
Первым аргументом получает
1. Альтернативные конструкторы
2. Корректная работа с наследованием
Если бы внутри было
Главное отличие в одной фразе
Если методу не нужен ни
Если нужно создать объект *того же класса, что вызвал метод* — это
🐍Вопросы с собесов -> ProstoPython
staticmethod vs classmethodОба — методы без
self. Оба вызываются через класс. Разница ловится за 30 секунд, если понять одну вещь.staticmethod — функция, которая просто живёт внутри класса
Не получает ни
self, ни cls. Не знает ни про экземпляр, ни про класс. По сути — обычная функция, которую сложили в namespace класса для удобства.
class Temperature:
@staticmethod
def c_to_f(celsius):
return celsius * 9/5 + 32
Temperature.c_to_f(100) # 212
Если убрать
staticmethod и вынести функцию наружу — ничего не сломается. Это и есть маркер.classmethod — метод, который знает свой класс
Первым аргументом получает
cls — сам класс. Это даёт две суперспособности:1. Альтернативные конструкторы
class User:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_dict(cls, data):
return cls(data["name"], data["age"])
user = User.from_dict({"name": "Anna", "age": 30})
cls(...) вместо User(...) — важная деталь. Об этом ниже.2. Корректная работа с наследованием
class Admin(User):
pass
admin = Admin.from_dict({"name": "Bob", "age": 40})
print(type(admin)) # <class 'Admin'> ✅
Если бы внутри было
return User(...) — Admin.from_dict(...) вернул бы User. Это классическая ошибка.Главное отличие в одной фразе
staticmethod — про группировку.classmethod — про полиморфизм по классу.Если методу не нужен ни
self, ни cls — это staticmethod.Если нужно создать объект *того же класса, что вызвал метод* — это
classmethod.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
🧰 Code Cleanup
Плохой код
Работает. Но это четыре строки там, где могла быть одна.
Чистый вариант
Та же логика, тот же ранний выход (
Зеркальный кейс — «все ли валидны»:
🐍Вопросы с собесов -> ProstoPython
Плохой код
def has_admin(users):
found = False
for user in users:
if user.role == "admin":
found = True
break
return found
Работает. Но это четыре строки там, где могла быть одна.
Чистый вариант
def has_admin(users):
return any(user.role == "admin" for user in users)
Та же логика, тот же ранний выход (
any ленивый — остановится на первом True), но без флага и break.Зеркальный кейс — «все ли валидны»:
# было
all_valid = True
for item in items:
if not item.is_valid():
all_valid = False
break
# стало
all_valid = all(item.is_valid() for item in items)
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🏆4
❌ Rookie Mistakes
Делаешь список функций — каждая должна возвращать своё число:
Ожидаешь:
Получаешь:
Почему так
Лямбда не «запоминает» значение
К моменту вызова
Это называется late binding — переменные в замыкании резолвятся в момент вызова, а не определения.
Как правильно
Способ 1 — захватить значение через дефолтный аргумент:
Дефолты вычисляются в момент определения функции —
Способ 2 —
Вывод
Замыкания захватывают переменные, а не значения. Если в цикле создаёшь функции — всегда фиксируй переменную явно (
🐍Вопросы с собесов -> ProstoPython
Делаешь список функций — каждая должна возвращать своё число:
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])
Ожидаешь:
[0, 1, 2]Получаешь:
[2, 2, 2]Почему так
Лямбда не «запоминает» значение
i в момент создания. Она запоминает саму переменную i. А переменная одна на всех.К моменту вызова
f() цикл уже завершился, и i равно последнему значению — 2. Все три лямбды смотрят в одну и ту же ячейку памяти.Это называется late binding — переменные в замыкании резолвятся в момент вызова, а не определения.
Как правильно
Способ 1 — захватить значение через дефолтный аргумент:
funcs = [lambda i=i: i for i in range(3)]
Дефолты вычисляются в момент определения функции —
i фиксируется.Способ 2 —
functools.partial:from functools import partial
funcs = [partial(lambda x: x, i) for i in range(3)]
Вывод
Замыкания захватывают переменные, а не значения. Если в цикле создаёшь функции — всегда фиксируй переменную явно (
x=x в аргументах или partial).🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍4
📈 From O(n) to O(1):
Задача
Дан массив. Приходят запросы вида «найди сумму элементов от индекса
Наивное решение — O(n) на запрос
Один запрос — O(n). Тысяча запросов на массиве в миллион — 10⁹ операций. Сервер плачет.
Идея: один раз посчитать, дальше отвечать за O(1)
Префиксная сумма
Тогда сумма на отрезке
Из суммы «всё до
Код
Препроцессинг — O(n), один раз.
Каждый запрос — O(1).
На тысяче запросов вместо 10⁹ операций получаем
🐍Вопросы с собесов -> ProstoPython
Задача
Дан массив. Приходят запросы вида «найди сумму элементов от индекса
l до r включительно». Запросов много — тысячи или миллионы.nums = [3, 1, 4, 1, 5, 9, 2, 6]
query(1, 4) → 1 + 4 + 1 + 5 = 11
query(2, 6) → 4 + 1 + 5 + 9 + 2 = 21
Наивное решение — O(n) на запрос
def query(nums, l, r):
return sum(nums[l:r+1])
Один запрос — O(n). Тысяча запросов на массиве в миллион — 10⁹ операций. Сервер плачет.
Идея: один раз посчитать, дальше отвечать за O(1)
Префиксная сумма
prefix[i] — это сумма всех элементов до индекса i.nums = [3, 1, 4, 1, 5, 9, 2, 6]
prefix = [0, 3, 4, 8, 9, 14, 23, 25, 31]
prefix[i] = сумма nums[0..i-1]. Длина — n + 1 (с нулём в начале для удобства).Тогда сумма на отрезке
[l, r] — это:prefix[r+1] - prefix[l]
Из суммы «всё до
r включительно» вычитаем «всё до l не включая». Остаётся ровно нужный отрезок.Код
def build_prefix(nums):
prefix = [0] * (len(nums) + 1)
for i, n in enumerate(nums):
prefix[i + 1] = prefix[i] + n
return prefix
def query(prefix, l, r):
return prefix[r + 1] - prefix[l]
Препроцессинг — O(n), один раз.
Каждый запрос — O(1).
На тысяче запросов вместо 10⁹ операций получаем
n + 1000 — фактически линейное время.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
❤4
🧠 Interview Thinking
Задача
Дана строка из скобок
Как думает junior
Сразу видит «надо считать скобки» и пишет:
Работает на
Junior часто радуется, что счётчик сошёлся, и не замечает, что порядок закрытия имеет значение.
Как думает сильный кандидат
Сначала задаёт себе вопрос: «Что я вообще проверяю?»
Не баланс чисел. А то, что каждая закрывающая скобка совпадает с последней открытой того же типа.
Это «последняя открытая, первая закрытая» — классический LIFO. Значит — стек.
Логика:
- открывающую — кидаем в стек
- закрывающую — смотрим, что наверху стека: если не пара, или стек пуст → не сбалансировано
- в конце стек должен быть пуст (если остались незакрытые — тоже false)
Тонкие моменты, которые отличают сильного кандидата
Сильный сам проговаривает edge cases, не дожидаясь вопроса:
- пустая строка →
- только закрывающие
- только открывающие
- смешанные с другими символами? → уточняет у интервьюера: «А в строке могут быть буквы, или только скобки?»
Последнее — особенно ценно. Уточнение требований до кода — маркер инженерного мышления.
Что хочет интервьюер
Не код. Код — побочный продукт.
Интервьюер слушает, как ты приходишь к идее стека. Идеальный путь:
1. «Проверять баланс счётчиком — недостаточно, порядок важен»
2. «Мне нужно помнить, что было открыто последним»
3. «Структура „последний вошёл — первый вышел“ — это стек»
4. «В Python это просто
Если ты пройдёшь этот путь вслух за 30 секунд — половина задачи решена ещё до первой строки кода.
🐍Вопросы с собесов -> ProstoPython
Задача
Дана строка из скобок
(), [], {}. Вернуть True, если они правильно сбалансированы."()[]{}" → True
"([{}])" → True
"(]" → False
"([)]" → False ← коварный кейс
"(((" → FalseКак думает junior
Сразу видит «надо считать скобки» и пишет:
def is_balanced(s):
count = 0
for ch in s:
if ch in "([{":
count += 1
else:
count -= 1
return count == 0
Работает на
"()()", ломается на "([)]" — там тоже баланс по числу, но порядок неправильный. Пройдёт по count == 0 и вернёт True. Бага.Junior часто радуется, что счётчик сошёлся, и не замечает, что порядок закрытия имеет значение.
Как думает сильный кандидат
Сначала задаёт себе вопрос: «Что я вообще проверяю?»
Не баланс чисел. А то, что каждая закрывающая скобка совпадает с последней открытой того же типа.
Это «последняя открытая, первая закрытая» — классический LIFO. Значит — стек.
def is_balanced(s):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in "([{":
stack.append(ch)
else:
if not stack or stack.pop() != pairs[ch]:
return False
return not stack
Логика:
- открывающую — кидаем в стек
- закрывающую — смотрим, что наверху стека: если не пара, или стек пуст → не сбалансировано
- в конце стек должен быть пуст (если остались незакрытые — тоже false)
Тонкие моменты, которые отличают сильного кандидата
Сильный сам проговаривает edge cases, не дожидаясь вопроса:
- пустая строка →
True (стек пуст в конце ✅)- только закрывающие
")))" → False (стек пуст)- только открывающие
"(((" → False (стек не пуст в конце)- смешанные с другими символами? → уточняет у интервьюера: «А в строке могут быть буквы, или только скобки?»
Последнее — особенно ценно. Уточнение требований до кода — маркер инженерного мышления.
Что хочет интервьюер
Не код. Код — побочный продукт.
Интервьюер слушает, как ты приходишь к идее стека. Идеальный путь:
1. «Проверять баланс счётчиком — недостаточно, порядок важен»
2. «Мне нужно помнить, что было открыто последним»
3. «Структура „последний вошёл — первый вышел“ — это стек»
4. «В Python это просто
list с append / pop»Если ты пройдёшь этот путь вслух за 30 секунд — половина задачи решена ещё до первой строки кода.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
❤4
🧰 Code Cleanup
Плохой код
Работает. Но каждый новый тип клиента — это +2 строки. И с каждым новым
Чистый вариант
Те же варианты, но:
- данные отделены от логики
- добавить новый тип = одна строка в словаре
-
А если нужно вызывать функции, а не возвращать значения?
Словарь умеет хранить и функции:
Вместо
Когда `if/elif` всё-таки лучше
Не каждую цепочку нужно превращать в словарь. Оставляй
- условия не равенство, а диапазоны или сложные проверки (
- веток 2–3 — словарь будет оверкиллом
- логика веток разная по структуре (где-то
🐍Вопросы с собесов -> ProstoPython
Плохой код
def get_discount(user_type):
if user_type == "regular":
return 0
elif user_type == "silver":
return 5
elif user_type == "gold":
return 10
elif user_type == "platinum":
return 20
else:
return 0
Работает. Но каждый новый тип клиента — это +2 строки. И с каждым новым
elif код всё дальше уезжает вправо.Чистый вариант
DISCOUNTS = {
"regular": 0,
"silver": 5,
"gold": 10,
"platinum": 20,
}
def get_discount(user_type):
return DISCOUNTS.get(user_type, 0)Те же варианты, но:
- данные отделены от логики
- добавить новый тип = одна строка в словаре
-
.get(..., 0) сам обрабатывает «дефолт», без elseА если нужно вызывать функции, а не возвращать значения?
Словарь умеет хранить и функции:
def handle_create(req): ...
def handle_update(req): ...
def handle_delete(req): ...
HANDLERS = {
"create": handle_create,
"update": handle_update,
"delete": handle_delete,
}
def dispatch(action, req):
handler = HANDLERS.get(action)
if handler is None:
raise ValueError(f"Unknown action: {action}")
return handler(req)
Вместо
if action == "create": handle_create(req) × N — одна строка диспетчеризации.Когда `if/elif` всё-таки лучше
Не каждую цепочку нужно превращать в словарь. Оставляй
if/elif, если:- условия не равенство, а диапазоны или сложные проверки (
age < 18, score >= 90)- веток 2–3 — словарь будет оверкиллом
- логика веток разная по структуре (где-то
return, где-то raise, где-то побочный эффект)🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4
Новая рубрика🔥🔥🔥
🎭 Red Flag — «Антипаттерн»
Плохие практики, которые выглядят как нормальный код
Мы стараемся для вас и работаем улучшением контента 🎉
🐍Вопросы с собесов -> ProstoPython
🎭 Red Flag — «Антипаттерн»
Плохие практики, которые выглядят как нормальный код
Мы стараемся для вас и работаем улучшением контента 🎉
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥3
🎭 Red Flag
Знакомая картина:
И в коде встречаешь такое:
Что здесь происходит — без подсказки IDE не разберёшь.
Почему это red flag
1. Невозможно читать на месте вызова.
Что значит
2. Флаг почти всегда означает «функция делает две разные вещи».
Это не одна функция. Это две функции, склеенные булевым переключателем. Внутри неизбежно появляется
3. Флаги размножаются.
Сначала один. Потом
Как надо
Способ 1 — разделить функцию на две.
Каждая делает одну вещь. Имя сразу говорит, что получишь.
Способ 2 — keyword-only аргументы.
Если флаг всё-таки нужен, заставь вызывающего писать его по имени:
Звёздочка
Способ 3 — Enum вместо булева.
Если флаг описывает не «да/нет», а выбор из вариантов — почти всегда это Enum, а не цепочка булевых:
Невозможно случайно передать два формата сразу. Невозможно забыть выбрать формат. Расширяется без новых аргументов.
Когда булев флаг норм
Не каждый булев — зло. Флаг ок, если:
- он действительно бинарный по смыслу
- их немного (1, максимум 2)
- передаётся по имени:
Правило
Каждый булев флаг в сигнатуре — это вопрос: *«а нельзя ли это разделить на две функции?»*
В половине случаев — можно. И код становится в разы понятнее.
🐍Вопросы с собесов -> ProstoPython
Знакомая картина:
def send_email(user, message, urgent=False, retry=False, log=False):
...
И в коде встречаешь такое:
send_email(user, msg, True, False, True)
Что здесь происходит — без подсказки IDE не разберёшь.
Почему это red flag
1. Невозможно читать на месте вызова.
Что значит
True, False, True? Каждый раз нужно открывать сигнатуру функции.2. Флаг почти всегда означает «функция делает две разные вещи».
def get_user(user_id, with_orders=False):
user = db.query(...)
if with_orders:
user.orders = db.query(...)
return user
Это не одна функция. Это две функции, склеенные булевым переключателем. Внутри неизбежно появляется
if with_orders — ветвление логики на каждый вызов.3. Флаги размножаются.
Сначала один. Потом
with_addresses, with_payments, include_deleted. Через полгода у функции 6 булевых аргументов и матрица из 64 поведений, из которых протестированы 3.Как надо
Способ 1 — разделить функцию на две.
def get_user(user_id): ...
def get_user_with_orders(user_id): ...
Каждая делает одну вещь. Имя сразу говорит, что получишь.
Способ 2 — keyword-only аргументы.
Если флаг всё-таки нужен, заставь вызывающего писать его по имени:
def send_email(user, message, *, urgent=False, retry=False):
...
send_email(user, msg, urgent=True, retry=False) # ✅ читаемо
send_email(user, msg, True, False) # ❌ TypeError
Звёздочка
* запрещает позиционную передачу. Магия — на месте вызова всегда понятно, что значит каждое значение.Способ 3 — Enum вместо булева.
Если флаг описывает не «да/нет», а выбор из вариантов — почти всегда это Enum, а не цепочка булевых:
# было
def render(template, html=False, json=False, xml=False): ...
# стало
class Format(Enum):
HTML = "html"
JSON = "json"
XML = "xml"
def render(template, format: Format): ...
Невозможно случайно передать два формата сразу. Невозможно забыть выбрать формат. Расширяется без новых аргументов.
Когда булев флаг норм
Не каждый булев — зло. Флаг ок, если:
- он действительно бинарный по смыслу
- их немного (1, максимум 2)
- передаётся по имени:
delete(path, force=True)subprocess.run(..., check=True) — нормально. process_data(True, False, True, False) — катастрофа.Правило
Каждый булев флаг в сигнатуре — это вопрос: *«а нельзя ли это разделить на две функции?»*
В половине случаев — можно. И код становится в разы понятнее.
🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
👍3
⏱️ Big O Breakdown
A) O(n)
B) O(n log n)
C) O(n²)
D) O(n) в среднем
Правильный ответ:C — O(n²)
Разбор
Цикл идёт по
Внутри
Итого — O(n²).
Глаз видит «один цикл», а на деле внутри
Маленькая правка — огромная разница
Сложность падает с O(n²) до O(n).
На миллионе элементов — разница между «отработало за 0.1 сек» и «висит уже 10 минут».
Тонкий момент: почему «в среднем»?
В реальной жизни этого почти не бывает — хеши в Python хорошо распределены, а словари автоматически расширяются. На собесе достаточно сказать: «O(1) amortized».
Вывод
Когда видишь
Если коллекция используется только для проверки «есть ли элемент» — почти всегда нужен
🐍Вопросы с собесов -> ProstoPython
def has_duplicates(items):
seen = []
for item in items:
if item in seen:
return True
seen.append(item)
return False
items — список длины n. Какая сложность?A) O(n)
B) O(n log n)
C) O(n²)
D) O(n) в среднем
Правильный ответ:
Разбор
Цикл идёт по
n элементам — это O(n).Внутри
item in seen для списка — это линейный поиск. В худшем случае пробегает весь список, чтобы убедиться, что элемента нет. С ростом seen это 1 + 2 + 3 + ... + n ≈ n²/2 операций.Итого — O(n²).
Глаз видит «один цикл», а на деле внутри
in спрятан второй.Маленькая правка — огромная разница
def has_duplicates(items):
seen = set() # ← вместо []
for item in items:
if item in seen:
return True
seen.add(item) # ← вместо append
return False
in для set — это O(1) в среднем (хеш-таблица).Сложность падает с O(n²) до O(n).
На миллионе элементов — разница между «отработало за 0.1 сек» и «висит уже 10 минут».
Тонкий момент: почему «в среднем»?
set и dict устроены на хеш-таблицах. В среднем доступ — O(1). Но в худшем случае (катастрофические коллизии хешей) — O(n).В реальной жизни этого почти не бывает — хеши в Python хорошо распределены, а словари автоматически расширяются. На собесе достаточно сказать: «O(1) amortized».
Вывод
Когда видишь
x in collection — сразу спрашивай себя: что это за коллекция?list / tuple → O(n)
set / dict → O(1) в среднем
Если коллекция используется только для проверки «есть ли элемент» — почти всегда нужен
set, а не list. Это одна из самых дешёвых оптимизаций в Python: меняешь [] на set(), и код ускоряется на порядки.🐍Вопросы с собесов -> ProstoPython
Telegram
Prosto Python | вопросы с собесов
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
🔥4