Направление
Подготовка к собеседованию — Full-Stack разработчик
Колода из 735+ карточек с вопросами для собеседования по направлению «Full-Stack разработчик» — по темам и уровню сложности, с возвратом ровно перед тем, как вы забудете. Посмотрите несколько карточек ниже, затем войдите, чтобы учить всё направление по расписанию в стиле Anki (SM-2).
Бесплатно · вход через GitHub · ваш прогресс остаётся с вами.
Что внутри
Все темы направления, сгруппированные так, как вы будете их учить.
Python
130 карточекDatabases
104 карточкиBackend
175 карточекFrontend
151 карточкаCS Fundamentals
76 карточекDevOps & Infra
35 карточекSystem Design
29 карточекBehavioral
35 карточекПримеры вопросов
Несколько карточек из колоды — откройте ответ, затем войдите, чтобы учить весь набор по расписанию.
Чем отличаются изменяемые (mutable) и неизменяемые (immutable) типы?
Чем отличаются изменяемые (mutable) и неизменяемые (immutable) типы?
Короткий ответ: Изменяемые объекты можно менять «на месте» без создания нового объекта (list, dict, set, bytearray), неизменяемые — нет (int, float, str, bytes, tuple, frozenset, bool, None). Любое «изменение» неизменяемого создаёт новый объект.
Подробно: Изменяемость — это про то, может ли объект поменять своё содержимое, сохранив тот же id() (адрес в памяти).
# Неизменяемый: операция создаёт НОВЫЙ объект
s = "hello"
print(id(s))
s += " world" # создаётся новая строка
print(id(s)) # id поменялся — это другой объект
# Изменяемый: меняется тот же объект
lst = [1, 2, 3]
print(id(lst))
lst.append(4) # меняем на месте
print(id(lst)) # id тот же
Почему это важно:
- Хешируемость. Только неизменяемые (по содержимому) объекты можно класть в
dict-ключи иset. Если бы ключ менялся, его хеш «уехал» бы, и его нельзя было бы найти. - Безопасность при шаринге. Неизменяемый объект можно безопасно расшарить между потоками/функциями — никто его не испортит.
- Аргументы функций. Передача изменяемого объекта означает, что функция может его поменять (см. вопрос про передачу аргументов).
⚠️ Ловушка: tuple неизменяем, но если в нём лежит list, этот список можно поменять:
t = ([1, 2], 3)
t[0].append(99) # OK! список внутри мутабелен
print(t) # ([1, 2, 99], 3)
# t[0] = [...] # вот ЭТО — TypeError, переприсвоить элемент нельзя
А ещё hash(([1,2], 3)) упадёт — кортеж нехешируем, если внутри есть нехешируемый элемент.
Чем отличается list от tuple?
Чем отличается list от tuple?
Короткий ответ: list изменяемый, tuple — нет. tuple хешируем (если все элементы хешируемы), занимает меньше памяти, чуть быстрее создаётся. list используют для однородных изменяемых коллекций, tuple — для фиксированных гетерогенных записей.
Подробно:
| Признак | list | tuple |
|---|---|---|
| Изменяемость | да | нет |
| Хешируемость | нет | да (если элементы хешируемы) |
| Память | больше (есть запас под рост) | меньше |
| Создание | медленнее | быстрее (литерал может кешироваться) |
| Семантика | коллекция однородных элементов | запись фиксированной структуры |
import sys
print(sys.getsizeof([1, 2, 3])) # ~88 байт
print(sys.getsizeof((1, 2, 3))) # ~64 байта
Почему list больше: он держит переаллокацию (over-allocation) — заранее выделяет место под будущие append, чтобы амортизировать стоимость роста до O(1). tuple фиксирован, ему запас не нужен.
Когда что выбирать:
tuple— когда количество и смысл элементов фиксированы: координаты(x, y), возврат нескольких значений из функции, ключ словаря.list— когда коллекция растёт/меняется/сортируется.
⚠️ Ловушка: «tuple быстрее» — правда лишь для создания литерала и доступа, но не магически во всём. И помни про вложенный мутабельный объект: tuple гарантирует неизменность ссылок, а не объектов, на которые они указывают.
Что такое хешируемость и зачем она нужна?
Что такое хешируемость и зачем она нужна?
Короткий ответ: Объект хешируем, если у него есть __hash__(), возвращающий стабильное значение за время жизни, и __eq__(). Хешируемость нужна, чтобы быть ключом dict или элементом set.
Подробно: Контракт: если a == b, то hash(a) == hash(b). Обратное не обязано выполняться (коллизии разрешены). По умолчанию пользовательские объекты хешируются по id().
hash((1, 2)) # ок
hash(frozenset())) # ок
# hash([1, 2]) # TypeError: unhashable type: 'list'
Если переопределяешь __eq__, то __hash__ автоматически становится None (объект перестаёт быть хешируемым) — Python защищает контракт. Нужно задать __hash__ явно:
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
__hash__ = lambda self: hash((self.x, self.y))
⚠️ Ловушка: Нельзя делать __hash__ по изменяемому полю. Если объект-ключ изменится после вставки, его хеш «уедет», и d[key] его не найдёт — он застрянет в словаре навсегда.
Как Python передаёт аргументы в функцию?
Как Python передаёт аргументы в функцию?
Короткий ответ: «Pass by object reference» (передача по ссылке на объект, она же «call by sharing»). В функцию передаётся ссылка на тот же объект. Мутабельный объект можно изменить изнутри, переприсваивание имени внутри — нет.
Подробно: Имя в Python — это ярлык на объект. При вызове параметр становится новым ярлыком на тот же объект.
def mutate(lst):
lst.append(99) # меняем сам объект → видно снаружи
def rebind(lst):
lst = [0, 0, 0] # переприсваиваем ЛОКАЛЬНОЕ имя → снаружи не видно
x = [1, 2]
mutate(x); print(x) # [1, 2, 99]
rebind(x); print(x) # [1, 2, 99] — не изменилось
То есть это не «pass by value» (копии нет) и не классический «pass by reference» (нельзя переприсвоить переменную вызывающего). Передаётся значение ссылки.
⚠️ Ловушка: Неизменяемые объекты создают иллюзию «pass by value»:
def inc(n): n += 1 # n = n + 1 создаёт новый int, имя локально
a = 5; inc(a); print(a) # 5 — не изменилось
Чтобы «вернуть» изменение неизменяемого — возвращай значение через return.
В чём разница между is и ==?
В чём разница между is и ==?
Короткий ответ: == сравнивает значения (вызывает __eq__). is сравнивает идентичность — это один и тот же объект в памяти (id(a) == id(b)).
Подробно:
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True — значения равны
a is b # False — разные объекты
c = a
a is c # True — один объект
is используют для сравнения с синглтонами: None, True, False.
if x is None: ... # правильно
if x == None: ... # работает, но плохой стиль и медленнее
⚠️ Ловушка: is иногда «случайно» совпадает с == из-за кеширования объектов (см. следующий вопрос), и новички пишут if x is 256 — оно работает в REPL для мелких чисел и ломается для крупных. Никогда не используй is для сравнения значений чисел/строк.
Что такое *args и **kwargs, и какие бывают виды аргументов?
Что такое *args и **kwargs, и какие бывают виды аргументов?
Короткий ответ: *args собирает лишние позиционные аргументы в кортеж, **kwargs — лишние именованные в словарь. В сигнатуре / отделяет positional-only, * — keyword-only аргументы.
Подробно:
def f(a, b, /, c, d, *args, e, f=10, **kwargs):
...
# ^^^^ ^^^^^ ^^^^^^^^^
# positional- обычные keyword-only (после *)
# only (до /)
- До
/— только позиционные (нельзя передать по имени). - После
*или*args— только по имени (keyword-only). *args→tuple,**kwargs→dict.
Распаковка при вызове:
def add(a, b, c): return a + b + c
nums = [1, 2, 3]
add(*nums) # распаковка списка в позиционные
d = {"a": 1, "b": 2, "c": 3}
add(**d) # распаковка словаря в именованные
Зачем positional-only (/): чтобы имена параметров не стали частью публичного API и их можно было переименовать. Зачем keyword-only (*): заставить вызывающего писать func(verbose=True) для читаемости и защиты от перепутанного порядка.
⚠️ Ловушка: Имена args/kwargs — соглашение, важны именно * и **. И порядок при распаковке нескольких источников должен быть валидным: f(*a, *b, **c, **d) допустимо, но ключи в **c/**d не должны конфликтовать.
Готовы закрепить навсегда?
Первая сессия — меньше минуты. Ваше будущее «я» на собеседовании скажет спасибо.