Prosto Python | вопросы с собесов
363 subscribers
184 photos
1 video
2 files
563 links
🚀 Python-собесы без сюрпризов! Разбираем реальные вопросы, ошибки кандидатов и лайфхаки, которые помогают пройти интервью. Джун → мидл → сеньор — прокачивайся и разнеси следующий собес! 🔥
Download Telegram
⏱️ Big O Breakdown

Посмотри на код:
def find_power(n):
count = 0
while n > 1:
n //= 2
count += 1
return count


Какая асимптотическая сложность?

A) O(n)
B) O(n²)
C) O(log n)
D) O(1)

Правильный ответ: C) O(log n)

🧠 Почему так:

На каждой итерации n делится на 2.

Если n = 1024:

1024 —> 512 —> 256 —> 128 —> 64 —> 32 —> 16 —> 8 —> 4 —> 2 —> 1

Сколько шагов?
Ровно столько, сколько раз можно делить число на 2.

Это и есть логарифм по основанию 2.

🐍Вопросы с собесов -> ProstoPython
👍4
Celery — инструмент для выполнения фоновых и распределённых задач в Python.

Основные сценарии

🔹 Отправка email
🔹 Обработка файлов (PDF, изображения, видео)
🔹 Долгие вычисления (отчёты, агрегации)
🔹 Интеграции с внешними API
🔹 Отложенные задачи (countdown, eta)
🔹 Периодические задачи (через Celery Beat)

Архитектурные задачи

🔹 Разгрузка HTTP-сервера
🔹 Горизонтальное масштабирование
🔹 Асинхронная обработка событий
🔹 Построение event-driven архитектуры

Когда использовать

🔹 Если задача выполняется долго
🔹 Если её не нужно выполнять синхронно в запросе
🔹 Если важна повторная попытка (retry) при ошибке

Итог:
Celery используется для фоновой, асинхронной и распределённой обработки задач вне HTTP-запроса.

🐍Вопросы с собесов -> ProstoPython
👍4
⏱️ Big O Breakdown

Посмотри на код:
def check_keys(d, keys):
for key in keys:
if key in d:
print("Found")


Какая сложность?

A) O(n)
B) O(n log n)
C) O(n²)
D) O(1)

(где n — количество элементов в keys)

Правильный ответ: A) O(n)

🧠 Разбираем:

Проверка key in d для словаря в среднем — O(1)
Потому что dict основан на хеш-таблице.

Цикл проходит по keys —> n раз.

Итого:
n × O(1) = O(n)

🐍Вопросы с собесов -> ProstoPython
🔥3
Rookie Mistakes
Тема: += и неожиданная мутация


Посмотри на код:
def add_item(item, items=[]):
items += [item]
return items

print(add_item(1))
print(add_item(2))


Многие думают, что += создаёт новый список.
Но результат будет:

[1]
[1, 2]


🧠 В чём ошибка?

items += [item] для списка — это in-place изменение.
Объект не создаётся заново.
И аргумент по умолчанию items=[] создаётся один раз при определении функции.

То есть список сохраняется между вызовами.

Новички думают:
+= это почти как items = items + [item]

Но это разные операции.
items = items + [item]   # создаёт новый объект
items += [item] # изменяет существующий


Правильный вариант:

def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items


🐍Вопросы с собесов -> ProstoPython
3
🧰 Code Cleanup — 25
Тема: двойной проход там, где можно за один


Иногда код выглядит аккуратно…
Но делает лишнюю работу.


Плохо:

def get_active_users(users):
active = [u for u in users if u.is_active]
return len(active)


Работает? Да.
Оптимально? Не совсем.


Ты создаёшь целый список,
хотя тебе нужно только количество.

Code Cleanup:
def get_active_users(users):
return sum(1 for u in users if u.is_active)


🧠 Почему так лучше:

🔹 нет лишнего списка в памяти
🔹 один проход без промежуточного хранения

🐍Вопросы с собесов -> ProstoPython
👍4
Static method — метод класса, который не получает self и cls.
Он логически относится к классу, но не зависит от его состояния.

Зачем нужен

🔹 Группировка утилитарной логики внутри класса
🔹 Семантическая связь функции с доменом класса
🔹 Избежание создания экземпляра

Пример
class MathUtils:
@staticmethod
def add(a, b):
return a + b

MathUtils.add(2, 3)


Метод не использует атрибуты класса или объекта


🐍Вопросы с собесов -> ProstoPython
👍4
⏱️ Big O Breakdown

Посмотри на код:
def has_divisor(n):
i = 1
while i * i <= n:
if n % i == 0:
return True
i += 1
return False


Какая сложность?

A) O(n)
B) O(log n)
C) O(√n)
D) O(n²)

Правильный ответ: C) O(√n)

🧠 Разбираем:

Цикл работает, пока i * i <= n.

Это значит:
i растёт до √n.

Если n = 1 000 000
то i дойдёт примерно до 1000.

Не до n.
Не до n/2.
А именно до √n.

🐍Вопросы с собесов -> ProstoPython
👍4
🧰 Code Cleanup — выпуск 26
Тема: лишний флаг в логике


Иногда код усложняют переменной-флагом, без которой можно обойтись.

Плохо:
def has_negative(nums):
found = False
for n in nums:
if n < 0:
found = True
return found


Работает? Да.
Но читается тяжелее, чем должен.


Code Cleanup:
def has_negative(nums):
for n in nums:
if n < 0:
return True
return False


Или ещё чище:
def has_negative(nums):
return any(n < 0 for n in nums)


🧠 Почему так лучше:

🔹 убираем лишнее состояние
🔹 код сразу выражает намерение
🔹 нет риска забыть обновить флаг

🐍Вопросы с собесов -> ProstoPython
💯41
Rookie Mistakes
Тема: путаница с изменяемыми объектами при умножении списка


Перед тобой такой код:

matrix = [[0] * 3] * 3

matrix[0][0] = 1

print(matrix)


Большинство ожидает:
[[1, 0, 0],
[0, 0, 0],
[0, 0, 0]]


Но реальный вывод будет:
[[1, 0, 0],
[1, 0, 0],
[1, 0, 0]]


🧠 В чём ошибка?


[[0] * 3] * 3
не создаёт три независимых списка.

Он создаёт один список
и три ссылки на него.

Ты меняешь один элемент
и изменения отражаются во всех строках.

Правильный способ:
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1
print(matrix)


Теперь каждая строка — отдельный объект


🐍Вопросы с собесов -> ProstoPython
👍4
Новая рубрика — 🧠 Interview Thinking

Здесь мы разбираем не просто задачи, а ход мыслей на собеседовании.
Как начинает рассуждать junior.
Как усиливает решение strong middle.
И какие сигналы интервьюер считывает во время ответа.

Код можно выучить.
А вот умение мыслить вслух, анализировать и улучшать своё решение — это уже уровень.


Мы стараемся постоянно придумывать новые форматы и разборы, чтобы подготовка была живой, разнообразной и максимально приближённой к реальным интервью.

Хочется, чтобы ты выходил на собес спокойным и уверенным в себе 🐍

🐍Вопросы с собесов -> ProstoPython
🔥41
🧠 Interview Thinking — выпуск 1

Тема: «Как найти цикл в связном списке?»

💬 Задача с собеса:
Дан head односвязного списка. Нужно определить, есть ли в нём цикл.

👶 Junior думает так:

«Буду сохранять все посещённые узлы в set.
Если узел уже встречался — значит цикл».

Работает? Да.
Сложность O(n).
Память O(n).

Нормально. Но не максимум.

🧑‍💻 Strong Middle думает глубже:

«Если есть цикл, можно использовать два указателя.
Один двигается на 1 шаг, второй на 2.
Если они встретятся — цикл есть».

Без дополнительной памяти.
Сложность O(n).
Память O(1).

🧠 Что реально хочет услышать интервьюер:

🔹Ты сначала предлагаешь рабочее решение

🔹Потом сам улучшаешь его

🔹Объясняешь trade-off по памяти

🐍Вопросы с собесов -> ProstoPython
👍4
CRUD — базовые операции над данными:

🔹 Create — создание новой записи в системе
🔹 Read — получение одной или нескольких записей
🔹 Update — изменение существующей записи
🔹 Delete — удаление записи

CRUD лежит в основе:
🔹 REST API
🔹 SQL-запросов
🔹 большинства backend-приложений

🐍Вопросы с собесов -> ProstoPython
🔥4
Новая рубрика — 📈 From O(n²) to O(n) — выпуск 1

Здесь мы берём рабочий, но неоптимальный код — и делаем его быстрее.

Цель простая: научиться видеть, где алгоритм «тормозит», и прокачать алгоритмическое мышление.

💬 Задача:
Проверить, есть ли в списке дубликаты.

Наивное решение:
def has_duplicates(nums):
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] == nums[j]:
return True
return False


Работает? Да.
Сложность? O(n²).


Два вложенных цикла.
На больших данных будет больно.

Улучшаем до O(n):
def has_duplicates(nums):
seen = set()
for num in nums:
if num in seen:
return True
seen.add(num)
return False


Теперь:
Проверка в set — O(1)
Один проход — O(n)

Итого O(n).

🧠 Что прокачиваем в этой рубрике:

🔹 умение замечать лишние вложенные проходы
🔹 понимание структур данных
🔹 привычку думать про масштабирование

🐍Вопросы с собесов -> ProstoPython
🏆6
⏱️ Big O Breakdown

Посмотри на код:
def strange(n):
i = n
while i > 1:
for j in range(i):
print(j)
i //= 2


Какая сложность?

A) O(n)
B) O(log n)
C) O(n log n)
D) O(n²)

Правильный ответ: A) O(n)

Да, не O(n log n). И вот почему 👇

🧠 Разбираем пошагово:

Первая итерация:
цикл на n операций

Вторая:
n/2

Третья:
n/4

Потом:
n/8
и так далее…

Суммарно получаем:

n + n/2 + n/4 + n/8 + ...

Это убывающая геометрическая прогрессия.
Её сумма стремится к 2n.

А значит итоговая сложность — O(n).

🐍Вопросы с собесов -> ProstoPython
👍4
Попробуйте ответить без запуска, что выведет код, разбор будет через 2 часа

🐍Вопросы с собесов -> ProstoPython
Правильный ответ: [1, 2, 3, 4]

🧠 Разбираем:

Строка:

lst += [4]

Для списка это in-place изменение.
Объект x действительно меняется.


А вот дальше:


lst = [0]

Это уже просто переопределение локальной переменной lst.
К исходному списку x это отношения не имеет.

Именно поэтому:

x стал [1, 2, 3, 4]

🔹присваивание [0] не повлияло на него

🐍Вопросы с собесов -> ProstoPython
👍5
Коллизия — ситуация, когда разные ключи дают одинаковый хеш или попадают в один индекс хеш-таблицы.

Как обходится

Open Addressing (открытая адресация)

🔹 Если ячейка занята — ищется следующая свободная

Используются стратегии:

🔹линейное пробирование

🔹квадратичное

🔹двойное хеширование

Так работает dict в Python.


🐍Вопросы с собесов -> ProstoPython
🔥4
📈 From O(n²) to O(n)
Задача: найти два числа в массиве, сумма которых равна target


Это одна из классических задач, которую очень любят на Python-собеседованиях.

Неоптимальное решение — O(n²)
nums = [3, 7, 2, 9, 7, 5]
target = 14

for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
print(i, j)


Идея простая:
для каждого элемента проверяем все остальные.

Работает.
Но у такого решения квадратичная сложность O(n²).


Если элементов станет много, программа начнёт работать заметно медленнее.

Оптимальное решение — O(n)

nums = [3, 7, 2, 9, 7, 5]
target = 14
seen = {}

for i, num in enumerate(nums):
need = target - num
if need in seen:
print(seen[need], i)
seen[num] = i


🧠 Что изменилось

🔹 массив проходим только один раз
🔹 используем словарь (hash map) для хранения уже просмотренных чисел
🔹 проверка in dict работает за O(1)

В итоге получаем линейную сложность O(n) вместо O(n²).

🐍Вопросы с собесов -> ProstoPython
👍41
Rookie Mistakes

Тема: изменяемый объект как значение по умолчанию

Это одна из самых коварных ошибок в Python.
Многие новички даже не подозревают, что здесь происходит.


Ошибка
def add_item(item, items=[]):
items.append(item)
return items

print(add_item(1))
print(add_item(2))
print(add_item(3))


На первый взгляд кажется, что функция каждый раз создаёт новый список.

Но вывод будет такой:
[1]
[1, 2]
[1, 2, 3]


🧠 Почему так происходит

Значения по умолчанию в Python создаются один раз — в момент определения функции, а не при каждом вызове.

Поэтому список items используется один и тот же при каждом вызове функции.


Правильный вариант
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items

print(add_item(1))
print(add_item(2))
print(add_item(3))


Теперь список будет создаваться каждый раз заново, и функция будет работать ожидаемо.

🐍Вопросы с собесов -> ProstoPython
👍4🏆1
В Python используется модель call by object reference (передача ссылки на объект).

Это означает:

🔹 В функцию передаётся ссылка на объект
🔹 Но сама ссылка передаётся по значению

в Python аргументы передаются по ссылке на объект, но переприсваивание внутри функции не влияет на внешний объект

🐍Вопросы с собесов -> ProstoPython
👍3