Начнём с цифры, которая объясняет, зачем вообще нужен этот текст. По данным hh.ru, hh.индекс в сфере информационных технологий подскочил с 9,6 в феврале 2025 года до 19,6 в феврале 2026-го. Перевод с языка эйчаров прост, конкуренция за место удвоилась, а собеседования стали жёстче. Медианная зарплата в отрасли при этом выросла скромно, с 89,9 тысячи до 100 тысяч рублей по стране, и это на фоне того, что ещё в 2022 и 2023 годах работодатели переманивали друг у друга рядовых разработчиков за тридцать процентов сверху. Рынок остыл. Вопросы на собеседованиях, наоборот, стали конкретнее.
Дальше сто вопросов, которые реально звучат в переговорках и на видеозвонках прямо сейчас, от бинарного поиска до вопроса «почему вы хотите уйти с текущего места». Собраны по десяти направлениям, алгоритмы, базы данных, сети, операционные системы, безопасность, веб-разработка, системный дизайн, DevOps, языки программирования и поведенческие вопросы. Никакой воды про важность непрерывного обучения (хотя один вопрос про это всё-таки есть, куда без него). Только то, что реально спрашивают и как на это отвечать, не потея.
Одно уточнение сразу. Идеального ответа не существует ни на один из ста вопросов ниже. Собеседующий чаще слушает не результат, а рассуждение. Кандидат, который вслух проговаривает «не уверен, но предположу, что», и рассуждает при этом правильно, обычно выигрывает у зазубрившего формулировку из учебника. Дальше как раз рассуждение и разбирается, а не шпаргалка для запоминания наизусть.
Алгоритмы и структуры данных
Первый блок проверяет не память, а понимание. Вызвать библиотечную функцию сортировки умеет любой. Объяснить, почему быстрая сортировка иногда скатывается в O(n²), умеет не каждый разработчик среднего уровня.
Почему бинарный поиск работает за O(log n)
Массив отсортирован, значит на каждом шаге можно отбросить половину кандидатов. Берём средний элемент, сравниваем с искомым значением и продолжаем поиск в левой или правой половине. После k шагов остаётся n делить на 2 в степени k элементов, а поиск заканчивается, когда это число доходит до единицы. Отсюда k равно логарифму n по основанию 2. Для миллиарда записей понадобится около тридцати сравнений вместо миллиарда при переборе по порядку. Условие ровно одно, массив должен быть отсортирован заранее. Забыли отсортировать, и вся экономия испаряется до банального линейного O(n).
Чем стек отличается от очереди и когда каждая структура уместнее
Стек работает по принципу LIFO, последним пришёл, первым вышел. Очередь работает по FIFO, первым пришёл, первым вышел. Стек пригождается для отмены действий (та самая комбинация Ctrl+Z), для рекурсии, где каждый вызов функции кладётся в стек вызовов, и для разбора скобочных выражений. Очередь нужна там, где важна честная последовательность обработки, например задачи в очереди печати или сообщения в Kafka. На собеседовании эту разницу путают редко, а вот с ходу привести пример из жизни, а не из учебника, получается не у всех.
Как работает быстрая сортировка и почему в худшем случае она скатывается до O(n²)
Quicksort выбирает опорный элемент, разбивает массив на элементы меньше и больше опорного, а затем рекурсивно сортирует обе части. В среднем это даёт O(n log n), потому что разбиение получается сбалансированным. Беда начинается, если опорный элемент выбран неудачно, например всегда берётся первый элемент уже отсортированного массива. Тогда одна из частей разбиения оказывается пустой, глубина рекурсии достигает n, и сложность деградирует до O(n²). Именно поэтому в реальных библиотеках Python и Java опорный элемент выбирают случайно или берут медиану из трёх кандидатов, чтобы такой сценарий стал маловероятным, а не гарантированным.
Что такое хеш-таблица и как разрешаются коллизии
Хеш-таблица хранит пары ключ-значение и вычисляет индекс ячейки через хеш-функцию от ключа. В идеале это даёт доступ за O(1). Проблема в том, что разные ключи иногда дают одинаковый хеш, отсюда коллизии. Решают их двумя способами. Метод цепочек хранит в каждой ячейке связный список всех ключей с таким хешем. Открытая адресация ищет следующую свободную ячейку по заданному правилу. Python в своих словарях использует открытую адресацию, а Java в HashMap при большом числе коллизий превращает список в дерево балансировки, чтобы не деградировать до O(n).
Как проверить, что строка читается одинаково в обе стороны, с учётом регистра и пробелов
Сначала строку нормализуют, приводят к одному регистру и убирают всё, что не буква и не цифра. Дальше сравнивают строку с её перевёрнутой версией, либо ставят два указателя, один в начале, другой в конце, и сдвигают их навстречу друг другу, сверяя символы на каждом шаге. Второй способ экономнее по памяти, потому что не создаёт перевёрнутую копию строки и работает за O(1) дополнительной памяти вместо O(n). Классическая ловушка для собеседования, фраза «А человек, а лошадь, а Панаму видал» после нормализации превращается в палиндром, а без неё нет.
В чём разница между связным списком и массивом
Массив хранит элементы в непрерывном участке памяти, поэтому доступ по индексу занимает O(1). Связный список хранит элементы вразброс, каждый содержит указатель на следующий, поэтому доступ по индексу требует прохода от начала и занимает O(n). Зато вставка в середину связного списка это O(1), если указатель на нужное место уже есть, а для массива это сдвиг всех последующих элементов, то есть O(n). Отсюда простое правило, нужен частый произвольный доступ, берут массив, нужны частые вставки и удаления в середине, берут связный список. На практике из-за особенностей кэша процессора массив часто выигрывает даже там, где теория обещает связному списку преимущество.
Что такое динамическое программирование, легче всего показать на числах Фибоначчи
Наивный рекурсивный расчёт Фибоначчи пересчитывает одни и те же подзадачи заново, и сложность растёт экспоненциально, до O(2^n). Динамическое программирование сохраняет результат каждой подзадачи и берёт его повторно вместо пересчёта. Для Фибоначчи это превращает экспоненту в линейный O(n). Общий принцип работает для любой задачи, где решение раскладывается на перекрывающиеся подзадачи с оптимальной подструктурой, отсюда и задача о рюкзаке, и поиск наибольшей общей подпоследовательности решаются тем же приёмом. Хороший ответ звучит не просто «использую мемоизацию», а как объяснение, почему именно перекрытие подзадач делает мемоизацию осмысленной.
Как найти цикл в связном списке
Алгоритм Флойда, он же «черепаха и заяц», ставит два указателя на начало списка. Один двигается на один элемент за шаг, второй на два. Если цикла нет, быстрый указатель дойдёт до конца списка. Если цикл есть, оба указателя рано или поздно окажутся в одной точке, потому что быстрый нагоняет медленного внутри цикла. Работает за O(n) по времени и O(1) по памяти, в отличие от альтернативы с хеш-множеством посещённых узлов, которая требует O(n) памяти. Название придумано не зря, это буквально гонка, где быстрый обязательно догонит медленного на замкнутой трассе.
Чем бинарное дерево поиска отличается от кучи
Бинарное дерево поиска хранит порядок, для каждого узла все значения слева меньше, все значения справа больше. Это даёт быстрый поиск, вставку и удаление за O(log n) в среднем случае, но при неудачных вставках дерево вырождается в список, и всё падает до O(n). Куча не хранит полный порядок, а гарантирует только одно, родитель больше или меньше обоих потомков. Зато у кучи есть гарантия сбалансированности по построению, поэтому извлечение минимума или максимума занимает O(1), а вставка O(log n). Дерево выбирают, когда нужен поиск произвольного элемента, кучу, когда нужен постоянный доступ к минимуму или максимуму, как в очереди с приоритетом.
Что такое амортизированная сложность и почему добавление в динамический массив в среднем O(1)
Динамический массив при переполнении выделяет новый участок памяти большего размера и копирует туда все элементы, а это разовая операция за O(n). Если бы каждое добавление требовало такого копирования, сложность была бы O(n) всегда. Хитрость в том, что размер увеличивают не на единицу, а с запасом, обычно удваивают. Тогда дорогая операция копирования случается всё реже по мере роста массива, и если посчитать суммарную стоимость n добавлений и разделить на n, получится O(1) в среднем, хотя отдельные вставки иногда стоят дорого. Это и называется амортизированной сложностью, не стоит путать её со средним случаем в обычном смысле.
Базы данных и SQL
Второй блок отделяет тех, кто писал SELECT * пять лет, от тех, кто понимает, что происходит при выполнении запроса до того, как вернётся результат.
Что такое нормализация базы данных и до какой формы доводят на практике
Нормализация убирает избыточность данных, разбивая таблицы так, чтобы каждый факт хранился в одном месте. Первая нормальная форма требует атомарных значений в ячейках, вторая убирает частичные зависимости от составного ключа, третья убирает транзитивные зависимости неключевых полей друг от друга. В реальных проектах почти всегда останавливаются на третьей нормальной форме, дальше идут скорее академические случаи. Иногда данные сознательно денормализуют обратно, дублируют поле, чтобы не делать лишний JOIN на каждый запрос. Это осознанный компромисс, а не ошибка проектирования.
В чём разница между INNER JOIN, LEFT JOIN и FULL OUTER JOIN
INNER JOIN возвращает только те строки, где есть совпадение в обеих таблицах. LEFT JOIN возвращает все строки левой таблицы, а если совпадения справа нет, подставляет NULL. FULL OUTER JOIN возвращает вообще все строки из обеих таблиц, дополняя NULL там, где совпадения не нашлось ни с одной стороны. Практический пример, таблица клиентов и таблица заказов. INNER JOIN покажет только клиентов, у которых есть хотя бы один заказ. LEFT JOIN покажет всех клиентов, включая тех, кто ничего не заказал, с пустыми полями заказа. PostgreSQL, кстати, FULL OUTER JOIN поддерживает нативно, а вот MySQL до сих пор нет, приходится эмулировать через UNION LEFT и RIGHT JOIN.
Что такое индекс в базе данных и почему их нельзя ставить везде
Индекс это отдельная структура данных, чаще всего B-дерево, которая хранит отсортированные значения столбца вместе со ссылками на строки таблицы. Без индекса база сканирует всю таблицу целиком, с индексом находит нужные строки за логарифмическое время. Цена платится при каждой записи, вставке, обновлении и удалении, потому что индекс приходится перестраивать вместе с таблицей. Вот почему индекс на таблицу, куда пишут в сто раз чаще, чем читают, скорее вредит, чем помогает. Хороший индекс ставится на столбцы, по которым реально фильтруют и сортируют в WHERE и ORDER BY, а не на всё подряд из суеверия.
Расскажите про ACID на примере транзакции
Атомарность гарантирует, что транзакция выполнится целиком либо не выполнится вовсе, при переводе денег со счёта на счёт нельзя списать с одного и не зачислить на другой. Согласованность требует, чтобы после транзакции база осталась в корректном состоянии по всем ограничениям и правилам. Изолированность не даёт параллельным транзакциям видеть промежуточные, ещё не зафиксированные изменения друг друга. Надёжность гарантирует, что после подтверждения транзакции данные переживут даже отключение питания сервера через секунду. Перевод денег в банке, пожалуй, самый честный пример ACID, потому что там любое из четырёх свойств легко сломать одной строчкой кода, и деньги либо исчезнут, либо задвоятся.
В чём разница между SQL и NoSQL, когда MongoDB предпочтительнее PostgreSQL
SQL-базы хранят данные в жёстко структурированных таблицах со схемой, заданной заранее, и гарантируют строгую согласованность через ACID. NoSQL-базы, документные вроде MongoDB, колоночные вроде Cassandra, графовые вроде Neo4j, жертвуют частью строгости ради гибкости схемы и горизонтального масштабирования. MongoDB имеет смысл, когда структура документа часто меняется и заранее неизвестна, например профили пользователей с произвольным набором полей. PostgreSQL остаётся разумным выбором по умолчанию для большинства бизнес-приложений, где данные реляционные по своей природе, заказы, платежи, счета, а согласованность важнее гибкости схемы.
Что такое проблема N+1 и как её избежать
Проблема возникает, когда код сначала одним запросом получает список из N записей, а затем для каждой из них делает отдельный запрос за связанными данными, итого N+1 обращений к базе вместо одного-двух. Классический случай, ORM получает список из ста постов блога одним запросом, а затем для каждого поста отдельно запрашивает автора, итого сто один запрос вместо двух. Лечится это через JOIN в исходном запросе или через явную предзагрузку связанных данных, в Django это select_related и prefetch_related. Проблема коварна тем, что на маленьких данных в разработке она незаметна, а в продакшене с реальным трафиком превращается в сотни лишних обращений к базе на каждую страницу.
Чем кластерный индекс отличается от некластерного
Кластерный индекс определяет физический порядок хранения строк на диске, поэтому на таблицу может быть только один такой индекс, обычно по первичному ключу. Некластерных индексов может быть сколько угодно, они хранятся отдельной структурой со ссылками на физическое расположение строк. Поиск по кластерному индексу быстрее, потому что данные уже лежат в нужном порядке, а поиск по некластерному требует дополнительного перехода к самой строке. В PostgreSQL, к слову, кластерного индекса в чистом виде нет, там все индексы вторичные, а физический порядок можно приблизить командой CLUSTER, но она не поддерживается автоматически при последующих вставках.
Что такое уровни изоляции транзакций
READ UNCOMMITTED допускает грязное чтение, то есть видит незафиксированные изменения других транзакций, и на практике почти нигде не используется. READ COMMITTED видит только зафиксированные данные, но одно и то же чтение внутри транзакции может дать разный результат, если параллельно кто-то успел закоммитить изменения. REPEATABLE READ фиксирует снимок данных на начало транзакции, и повторное чтение внутри неё всегда даёт одинаковый результат, но возможны фантомные строки, если кто-то добавил новые записи под условие запроса. SERIALIZABLE исключает все аномалии, транзакции выполняются так, будто идут строго друг за другом, ценой самой низкой пропускной способности. PostgreSQL по умолчанию использует READ COMMITTED, и большинство разработчиков даже не подозревают, что живут именно на этом уровне.
Как работает репликация в PostgreSQL, синхронная и асинхронная
Репликация копирует данные с основного сервера на один или несколько резервных, обычно через передачу журнала предзаписи (WAL). При асинхронной репликации основной сервер подтверждает транзакцию клиенту, не дожидаясь, пока реплика получит и применит изменения, отсюда небольшая задержка и риск потерять последние секунды данных при внезапном падении основного сервера. При синхронной репликации основной сервер ждёт подтверждения хотя бы от одной реплики перед тем, как сказать клиенту об успехе, это убирает риск потери данных, но увеличивает задержку каждой записи. Банковские системы обычно выбирают синхронную репликацию хотя бы для критичных таблиц, соцсети чаще довольствуются асинхронной ради скорости.
Что такое шардирование и чем оно отличается от партиционирования
Партиционирование разбивает одну логическую таблицу на физические части внутри одной базы данных, например по диапазону дат или по региону, но всё это остаётся на одном сервере. Шардирование идёт дальше и разносит части данных по разным серверам целиком, каждый шард хранит свой независимый кусок данных и обслуживает свою часть нагрузки. Партиционирование решает проблему слишком большой таблицы на одной машине, шардирование решает проблему, когда одной машины уже физически не хватает ни по объёму, ни по нагрузке. Цена шардирования, кросс-шардовые запросы между данными на разных серверах становятся дорогими или вовсе невозможными без дополнительной логики на уровне приложения.
Сети и протоколы
Третий блок половина собеседований в системном администрировании и добрая треть в бэкенде. Отвечать шаблонно про семь уровней OSI можно, но собеседующего давно не удивляет зазубренная модель, ему интереснее, понимает ли кандидат, что происходит с пакетом на самом деле.
Опишите TCP three-way handshake, зачем нужен третий пакет
Клиент отправляет пакет SYN с начальным номером последовательности. Сервер отвечает пакетом SYN-ACK, подтверждая получение и добавляя свой собственный номер последовательности. Клиент отправляет третий пакет ACK, подтверждая номер сервера. Без третьего пакета сервер не может быть уверен, что клиент реально получил его ответ, ведь SYN-ACK мог просто потеряться в сети. Трёхстороннее рукопожатие устанавливает не только соединение, но и синхронизирует начальные номера последовательности в обе стороны, что нужно для правильной сборки последующих пакетов и защиты от случайного дублирования старых соединений.
В чём разница между TCP и UDP, почему видеозвонки используют UDP
TCP гарантирует доставку, порядок пакетов и повторную отправку потерянных сегментов, ценой дополнительной задержки на подтверждения и повторы. UDP ничего не гарантирует, отправил пакет и забыл, зато почти без накладных расходов. Для видеозвонка устаревший кадр хуже, чем его отсутствие, ведь ждать повторной отправки потерянного пакета из TCP означает зависание картинки на секунду, пока сеть разберётся с ретрансмиссией. UDP просто пропускает потерянный кадр и идёт дальше, изображение на долю секунды дёргается, но звонок не замирает. Отсюда правило, где важна точность и полнота, там TCP, где важна скорость и свежесть данных больше, чем идеальная точность, там UDP.
Что происходит, когда вводишь URL в браузере и жмёшь Enter
Браузер сначала разбирает URL и проверяет кэш DNS, если адреса там нет, идёт запрос к DNS-серверу, который резолвит доменное имя в IP-адрес. Дальше браузер устанавливает TCP-соединение с сервером через three-way handshake, а если адрес начинается с https, сверху накручивается ещё и TLS handshake для шифрования канала. После этого браузер отправляет HTTP-запрос, сервер обрабатывает его, возможно обращаясь к базе данных и другим сервисам, и возвращает ответ с HTML-документом. Браузер начинает парсить HTML, строит DOM-дерево, параллельно запрашивает CSS и JavaScript, и наконец рендерит страницу на экране. Вопрос звучит банально, но за минуту грамотного ответа видно сразу знание сетей, HTTP и основ рендеринга.
Чем HTTP/1.1 отличается от HTTP/2 и HTTP/3
HTTP/1.1 открывает по одному TCP-соединению на запрос или ограниченное число параллельных соединений, из-за чего возникает head-of-line blocking, один медленный запрос тормозит остальные в очереди. HTTP/2 вводит мультиплексирование, несколько запросов и ответов идут по одному TCP-соединению одновременно, плюс сжатие заголовков и приоритизацию потоков. Проблема в том, что head-of-line blocking всё равно остаётся, только теперь на уровне TCP, потеря одного пакета блокирует все мультиплексированные потоки в этом соединении. HTTP/3 решает это, отказавшись от TCP в пользу QUIC поверх UDP, где потеря пакета в одном потоке не блокирует остальные. Большая часть трафика Google уже давно работает через QUIC, хотя многие серверы всё ещё дают откат на HTTP/2 при проблемах с UDP на сети клиента.
Что такое DNS и как работает рекурсивный резолвинг
DNS переводит доменные имена в IP-адреса, потому что маршрутизаторы работают с числами, а не с именами вроде habr.com. Рекурсивный резолвер, обычно у интернет-провайдера, получает запрос от клиента и берёт на себя всю дальнейшую работу. Сначала он спрашивает корневой сервер, тот отправляет к серверу зоны .com, тот отправляет к авторитетному серверу конкретного домена, и только там находится финальный IP-адрес. Результат кэшируется на время, заданное значением TTL, чтобы не гонять этот путь заново при каждом запросе. Без кэширования DNS весь интернет замедлился бы в разы, ведь один и тот же путь к корневым серверам пришлось бы проходить на каждый визит любого сайта.
Опишите модель OSI, зачем она нужна на практике
Модель состоит из семи уровней, от физического до прикладного.
| Уровень | Название | Пример |
|---|---|---|
| 7 | Прикладной | HTTP, DNS, SMTP |
| 6 | Представления | шифрование, кодировки |
| 5 | Сеансовый | установление и поддержание сессии |
| 4 | Транспортный | TCP, UDP |
| 3 | Сетевой | IP, маршрутизаторы |
| 2 | Канальный | Ethernet, MAC-адреса, коммутаторы |
| 1 | Физический | кабели, радиоволны, разъёмы |
На практике весь современный интернет работает по модели TCP/IP из четырёх уровней, а не по классической семиуровневой OSI. Модель OSI при этом никуда не делась из профессионального жаргона, потому что даёт общий словарь. Когда сетевой инженер говорит «проблема на третьем уровне», все понимают, что дело в маршрутизации, а не в физическом кабеле или в приложении. Собеседующему обычно важнее не заучивание всех семи названий, а способность сказать, на каком уровне решается конкретная проблема, скажем DNS живёт на прикладном уровне, а коммутатор работает на канальном.
Что такое NAT и зачем он нужен при нехватке IPv4-адресов
Адресов IPv4 всего около четырёх миллиардов, и они закончились ещё в начале 2010-х, а устройств в сети давно больше. NAT позволяет множеству устройств в локальной сети выходить в интернет через один-единственный публичный IP-адрес маршрутизатора. Устройства внутри сети получают приватные адреса, маршрутизатор подменяет исходный адрес и порт на исходящих пакетах и запоминает соответствие, чтобы правильно вернуть ответ нужному устройству. Побочный эффект, входящее соединение снаружи напрямую до устройства за NAT не дотянется без дополнительной настройки, что заодно работает как простейшая защита от прямых атак на домашние устройства.
В чём разница между симметричным и асимметричным шифрованием в TLS handshake
Симметричное шифрование использует один и тот же ключ для шифрования и расшифровки, работает быстро, но требует безопасно передать этот ключ обеим сторонам заранее. Асимметричное шифрование использует пару ключей, публичный и приватный, зашифрованное публичным ключом расшифровывается только приватным, что решает проблему передачи ключа, но работает заметно медленнее на больших объёмах данных. TLS handshake берёт лучшее от обоих подходов, асимметричное шифрование используется только в самом начале, чтобы стороны безопасно договорились об общем симметричном ключе сессии, а весь дальнейший обмен данными идёт уже быстрым симметричным шифрованием. Именно поэтому HTTPS не создаёт заметной задержки на каждый запрос, дорогая асимметричная часть происходит один раз при установке соединения.
Что такое VPN, чем WireGuard отличается от OpenVPN
VPN создаёт зашифрованный туннель между устройством и удалённым сервером, весь трафик идёт через этот туннель, а для внешнего наблюдателя видно только зашифрованный поток к серверу VPN, а не к реальным сайтам. OpenVPN существует с начала 2000-х, работает поверх собственного протокола на базе TLS, гибко настраивается, но код ядра занимает порядка ста тысяч строк, что усложняет аудит безопасности. WireGuard, первый релиз которого вышел в 2016 году, а с 2020-го он встроен прямо в ядро Linux, написан заметно компактнее, около четырёх тысяч строк кода, использует современную фиксированную криптографию и в бенчмарках обычно быстрее OpenVPN в разы. Компактный код тут не признак недоделанности, а прямое следствие философии проекта, меньше кода, меньше поверхность для уязвимостей.
Как работает балансировка нагрузки, зачем нужны health checks
Балансировщик распределяет входящие запросы между несколькими серверами по одному из алгоритмов. Round robin отправляет запросы по кругу, независимо от текущей загрузки серверов. Least connections отправляет следующий запрос серверу с наименьшим числом активных соединений, что честнее при разной длительности запросов. Health check это отдельный периодический запрос, обычно к специальному эндпоинту вроде /health, которым балансировщик проверяет, жив ли сервер и готов ли принимать трафик. Без health check балансировщик продолжит слать запросы на упавший сервер, и часть пользователей будет получать ошибки, пока кто-то вручную не заметит проблему. С health check упавший узел исключается из ротации автоматически за секунды.
Операционные системы и Linux
Четвёртый блок про то, что происходит между нажатием клавиши и выводом на экран. На системного администратора и на бэкенд-разработчика тут спрашивают почти одно и то же, разве что глубина ожидается разная.
В чём разница между процессом и потоком
Процесс это независимая единица выполнения со своим собственным адресным пространством памяти, файловыми дескрипторами и ресурсами. Поток существует внутри процесса и делит с другими потоками того же процесса общую память, но имеет собственный стек вызовов и счётчик команд. Создать новый процесс дороже, потому что операционной системе нужно выделить и изолировать отдельное адресное пространство, создать новый поток дешевле, потому что память уже общая. Плата за эту дешевизну, потоки одного процесса легко ломают данные друг друга при неаккуратной работе с общими переменными, отсюда вся боль синхронизации, мьютексов и гонок данных, которой процессы избегают просто за счёт изоляции.
Что такое дедлок, какие условия должны совпасть
Дедлок это ситуация, когда два или больше потоков ждут друг друга бесконечно и ни один не может продолжить работу. Для этого одновременно должны выполниться четыре условия, названные ещё в 1971 году Эдвардом Коффманом. Взаимное исключение, когда ресурс занят только одним потоком за раз. Удержание с ожиданием, поток держит один ресурс и одновременно ждёт другой. Неотбираемость, ресурс нельзя силой отобрать у потока, который его держит. И циклическое ожидание, поток А ждёт ресурс, занятый потоком Б, а Б в это же время ждёт ресурс, занятый А. Убрать хотя бы одно условие, и дедлок становится невозможен в принципе, самый практичный способ, всегда захватывать ресурсы в одном заранее оговорённом порядке.
Как работает виртуальная память, зачем нужен своп
Виртуальная память даёт каждому процессу иллюзию собственного непрерывного адресного пространства, хотя физически данные разбросаны по оперативной памяти страницами, а таблица страниц процессора переводит виртуальные адреса в физические на лету. Это даёт изоляцию процессов друг от друга и позволяет запускать программы, которые в сумме требуют больше памяти, чем реально стоит в сервере. Своп это область на диске, куда операционная система выгружает редко используемые страницы памяти, когда физической оперативки не хватает, освобождая место для активных процессов. Плата очевидна, диск на порядки медленнее оперативной памяти, поэтому активный своп это почти всегда сигнал, что серверу физически не хватает RAM, а не признак хорошо настроенной системы.
Чем chmod 755 отличается от 644, что такое suid и sgid
Права доступа в Linux кодируются тремя цифрами для владельца, группы и остальных, где каждая цифра это сумма чтения (4), записи (2) и выполнения (1). Режим 755 даёт владельцу полный доступ, а группе и всем остальным только чтение и выполнение, это типичные права для исполняемого файла или папки. Режим 644 даёт владельцу чтение и запись, а остальным только чтение, стандартно для обычного файла без исполнения. Бит suid, если установлен на исполняемом файле, запускает программу с правами владельца файла, а не запустившего пользователя, классический случай, команда passwd должна писать в системный файл паролей от имени root, даже когда её запускает обычный пользователь. Именно suid-биты чаще всего ищут в первую очередь при аудите повышения привилегий, потому что неудачно настроенный suid на нужном бинарнике превращается в путь к root.
Что делает kill -9, почему это грубый способ завершить процесс
Команда kill -9 отправляет процессу сигнал SIGKILL, который операционная система обрабатывает на уровне ядра и который процесс не может ни перехватить, ни проигнорировать, ни обработать по-своему. Обычный kill без флага отправляет SIGTERM, вежливую просьбу завершиться, которую процесс может перехватить и корректно закрыть файлы, сохранить состояние, отключиться от базы данных. SIGKILL обрывает процесс мгновенно, независимо от того, что он в этот момент делал, отсюда риск, недописанный файл, незакрытая транзакция в базе, повреждённые временные данные. Хорошая практика, сначала пробовать SIGTERM и подождать несколько секунд, и только если процесс завис и не реагирует, переходить к SIGKILL как к крайней мере.
Чем hard link отличается от symbolic link
Жёсткая ссылка это ещё одно имя для того же самого inode, то есть для тех же самых данных на диске, оригинал и ссылка неотличимы друг от друга, а данные удаляются физически только когда счётчик ссылок на inode доходит до нуля. Символическая ссылка это отдельный маленький файл, который просто хранит путь к другому файлу, и если оригинал удалить, символическая ссылка останется, но будет указывать в никуда. Жёсткие ссылки нельзя создать на директорию и нельзя протянуть между разными файловыми системами, символические можно делать хоть на папку, хоть через границы разных дисков. На практике symlink используют для гибких путей и версионирования, hard link, когда нужно физически сэкономить место без дублирования данных.
Что такое зомби-процесс, откуда он берётся
Когда процесс завершается, он не исчезает мгновенно, а превращается в запись в таблице процессов с кодом завершения, ожидая, пока родительский процесс явно заберёт этот код через системный вызов wait. Именно эта промежуточная запись и называется зомби-процессом, он уже не выполняет код и не занимает память, но всё ещё занимает место в таблице процессов. Проблема возникает, если родительский процесс никогда не вызывает wait, зомби копятся, и при большом количестве они забивают таблицу процессов и не дают создавать новые. Решается это либо исправлением родительского кода, либо, если родитель сам умер, зомби автоматически усыновляются процессом init или systemd, который и подчищает их за родителя.
Как посмотреть, какой процесс занимает порт 8080, что делать дальше
В Linux для этого есть команда lsof -i :8080, либо более современная ss -tulpn | grep 8080, обе покажут PID процесса, который слушает нужный порт. Дальше есть варианты. Если процесс лишний и его можно убить, дальше идёт обычный kill с этим PID, сначала мягкий, потом при необходимости SIGKILL. Если порт занят чем-то важным и конфликт возник из-за того, что два сервиса настроены на один и тот же порт, разумнее поменять порт в конфигурации нового сервиса, а не убивать работающий процесс наугад. Частая ошибка новичка, увидеть незнакомый PID и сразу его прибить, не проверив, что именно там запущено, иногда это оказывается вовсе не тестовый сервер, а что-то важное для соседней команды.
Чем cron отличается от systemd timer
Cron это классический демон планирования задач, который читает расписание из crontab в простом текстовом формате из пяти полей и запускает команды по расписанию. Systemd timer делает то же самое, но встроен в systemd и даёт больше возможностей, зависимости от других юнитов systemd, гибкое логирование через journalctl, возможность отложить запуск, если система была выключена в момент, когда задача должна была сработать. Cron проще и привычнее, особенно на старых или минималистичных системах, systemd timer удобнее там, где и так уже вся система управляется через systemd и хочется единообразия в логировании и мониторинге задач. На современных дистрибутивах вроде Ubuntu оба варианта работают одновременно, и выбор часто определяется просто личными привычками команды.
Что такое переключение контекста, почему оно не бесплатно
Переключение контекста происходит, когда процессор перестаёт выполнять один поток или процесс и начинает выполнять другой, для этого нужно сохранить всё текущее состояние первого и загрузить сохранённое состояние второго. Само сохранение и загрузка занимают время, обычно от одной до нескольких десятков микросекунд, в зависимости от процессора. Дороже другое, переключение почти всегда сбрасывает содержимое кэша процессора, который был заполнен данными предыдущего потока, а новый поток начинает работу с холодным кэшем, и первые обращения к памяти идут заметно медленнее, пока кэш снова не прогреется. Именно поэтому слишком частое переключение между большим числом потоков снижает реальную производительность даже без единой ошибки в коде, накладные расходы съедаются на самом переключении, а не на полезной работе.
Информационная безопасность
Пятый блок, пожалуй, самый живой в 2026 году. По данным аналитиков рынка труда, информационная безопасность остаётся едва ли не единственным сегментом ИТ, которого не коснулось общее охлаждение найма. Спрашивают тут жёстко и предметно, часто с реальными инцидентами в качестве примера.
Что такое SQL-инъекция, как параметризованные запросы её предотвращают
SQL-инъекция происходит, когда пользовательский ввод подставляется прямо в текст SQL-запроса без экранирования, и злоумышленник вписывает туда фрагмент, который меняет логику запроса. Классический случай, поле логина с содержимым вроде ' OR '1'='1, вставленное в необработанный запрос, превращает проверку пароля в условие, которое истинно всегда. Параметризованные запросы решают проблему на уровне архитектуры, а не фильтрации, потому что структура запроса и пользовательские данные передаются в базу отдельно друг от друга, драйвер базы данных никогда не интерпретирует значение параметра как часть SQL-синтаксиса, что бы там ни было написано. Именно поэтому современный совет звучит не «экранируйте кавычки», а «вообще не собирайте SQL из строк».
В чём разница между аутентификацией и авторизацией
Аутентификация отвечает на вопрос, кто ты, и проверяет это через пароль, отпечаток пальца, одноразовый код или их комбинацию. Авторизация отвечает на другой вопрос, что тебе разрешено делать, уже после того, как личность подтверждена. Спутать их легко, потому что оба процесса обычно происходят подряд и незаметно для пользователя, но разница принципиальна для безопасности. Пользователь может успешно пройти аутентификацию, но при этом получить отказ в авторизации на конкретное действие, скажем, удаление чужого аккаунта. Уязвимости, где эти два понятия путают в коде, называются broken access control и по данным OWASP годами держатся в первой строчке списка самых распространённых уязвимостей веб-приложений.
Что такое XSS, чем хранимый отличается от отражённого
XSS позволяет злоумышленнику внедрить вредоносный JavaScript-код на страницу, которую потом откроет другой пользователь, и код выполнится в его браузере от имени этого сайта. Отражённый XSS живёт только в одном конкретном запросе, например вредоносный скрипт зашит прямо в ссылку, которую жертва должна кликнуть, а сервер без проверки отражает содержимое параметра обратно в HTML-страницу. Хранимый XSS опаснее, потому что вредоносный код один раз сохраняется на сервере, например в комментарии к посту, и затем автоматически выполняется у каждого, кто откроет эту страницу, без единого клика по специальной ссылке. Базовая защита от обоих видов одна и та же, экранирование пользовательского ввода перед выводом в HTML и заголовок Content-Security-Policy, который ограничивает, откуда странице вообще разрешено загружать исполняемый код.
Как работает двухфакторная аутентификация, почему SMS слабый второй фактор
Двухфакторная аутентификация требует подтверждения личности через два разных типа факторов, обычно что-то известное (пароль) и что-то, чем пользователь физически владеет, телефон или аппаратный токен. SMS-код формально второй фактор, но слабый по нескольким причинам. SIM-swapping позволяет злоумышленнику перевыпустить SIM-карту жертвы через социальную инженерию оператора связи и получать её SMS напрямую. Протокол SS7, через который маршрутизируются SMS между операторами, известен уязвимостями с середины 2010-х и теоретически позволяет перехватывать сообщения без физического доступа к телефону. Приложения-аутентификаторы и аппаратные ключи вроде YubiKey не зависят от сотовой сети и потому считаются заметно надёжнее, хотя внедрять их сложнее с точки зрения пользовательского опыта.
Что такое CSRF-атака, как токены её предотвращают
CSRF заставляет браузер жертвы отправить запрос на сайт, где жертва уже авторизована, от её имени и без её ведома, обычно через скрытую форму или картинку на стороннем вредоносном сайте. Браузер автоматически прикладывает к запросу куки авторизации, потому что для него это ничем не отличается от обычного запроса к знакомому домену, а сервер без дополнительной проверки не может отличить намеренное действие пользователя от подделанного чужой страницей. CSRF-токен решает проблему тем, что сервер выдаёт странице уникальное случайное значение, которое должно прийти обратно вместе с формой, а у злоумышленника на чужом сайте просто нет доступа к этому значению. Дополнительно современные браузеры смягчают проблему атрибутом cookie SameSite, который по умолчанию не даёт куки уходить вместе с запросами с чужих доменов.
Расскажите о принципе наименьших привилегий
Принцип требует давать каждому пользователю, сервису и учётной записи ровно тот минимум прав, который нужен для выполнения задачи, и ни битом больше. Атака на Change Healthcare в феврале 2024 года хорошо показывает, что бывает, когда это правило игнорируют сразу в нескольких местах. Начиналось всё с украденных учётных данных от Citrix-портала без многофакторной аутентификации, но масштаб ущерба обеспечило другое, атакующие девять дней перемещались по сети куда дальше, чем должна была доставать обычная учётная запись, и добрались до данных примерно ста девяноста миллионов человек. Правильная сегментация сети и урезанные права именно этой учётной записи ограничили бы зону поражения одного скомпрометированного аккаунта. На бытовом уровне тот же принцип, разработчику для отладки редко нужны продовые данные, а сервису для чтения статистики редко нужны права на удаление таблиц.
Что такое уязвимость нулевого дня, чем отличается от уже известной, но не пропатченной
Уязвимость нулевого дня это уязвимость, о которой ещё не знает производитель ПО, соответственно у него было ноль дней на выпуск патча, отсюда и название. Как только производитель узнаёт о проблеме и выпускает исправление, уязвимость перестаёт быть нулевым днём, даже если конкретная организация патч ещё не установила. Вторая ситуация встречается на порядок чаще первой, огромное большинство успешных атак используют давно известные и давно пропатченные уязвимости, просто жертва не успела или не захотела обновиться. Zero-day опаснее в моменте, потому что защититься от неизвестной угрозы штатными средствами почти невозможно, зато непропатченная старая уязвимость опаснее в масштабе, ведь таких дыр инфраструктура компании в среднем копит месяцами, и любой может их просканировать и найти.
Как работает хеширование паролей, почему нельзя хранить MD5
Хороший подход хранит не сам пароль, а результат однонаправленной хеш-функции от него, вместе со случайной солью, уникальной для каждого пользователя. При входе введённый пароль хешируют той же солью и сравнивают результат с сохранённым значением, сам пароль при этом нигде не хранится в открытом виде. MD5 для этой задачи не годится по двум причинам. Функция была разработана в начале девяностых для контроля целостности данных, а не для защиты паролей, и работает специально быстро, что удобно для проверки контрольных сумм, но ужасно для защиты, потому что современная видеокарта перебирает миллиарды вариантов MD5 в секунду. Bcrypt и argon2, победивший в конкурсе Password Hashing Competition в 2015 году, спроектированы специально медленными и прожорливыми по памяти, чтобы массовый перебор оставался дорогим даже для атакующего с фермой видеокарт.
Что такое supply chain атака, пример SolarWinds
Атака на цепочку поставок компрометирует не саму жертву напрямую, а доверенного поставщика ПО или библиотеку, которую жертва и тысячи других компаний устанавливают сами, добровольно, как обычное обновление. Классический случай, взлом SolarWinds в 2020 году, когда атакующие внедрили вредоносный код прямо в обновление платформы мониторинга Orion, и это обновление затем установили около восемнадцати тысяч организаций, включая несколько министерств США. Атака опасна именно масштабом доверия, компания тщательно проверяет собственный периметр, но крайне редко проверяет байт за байтом код от поставщика с подписанным сертификатом и репутацией. После 2020 года индустрия заметно активнее заговорила о SBOM, списке всех компонентов внутри приложения, именно как о попытке ответить на этот класс атак.
Чем EDR отличается от обычного антивируса
Классический антивирус в основном сравнивает файлы с базой сигнатур известных вредоносных программ и блокирует совпадения, это неплохо работает против уже известных угроз, но бессилен против нового или слегка изменённого вредоноса. EDR следит не столько за файлами, сколько за поведением, подозрительными цепочками процессов, необычной сетевой активностью, попытками закрепиться в системе, и умеет реагировать в реальном времени, изолируя устройство от сети при обнаружении угрозы. Обратная сторона такой глубокой интеграции с ядром операционной системы хорошо видна на примере CrowdStrike, чей продукт Falcon Sensor 19 июля 2024 года из-за ошибки в обновлении содержимого вызвал массовые синие экраны на миллионах Windows-компьютеров по всему миру, остановив рейсы Delta, United и American Airlines и задев банки и больницы в десятках стран. Инцидент не был взломом, но стал наглядным уроком, чем глубже защитное ПО сидит в ядре системы ради возможностей EDR, тем дороже обходится любая его собственная ошибка.
Веб-разработка и API
Шестой блок обычно достаётся фронтенд- и бэкенд-разработчикам поровну, и именно тут чаще всего путают теоретическое знание протокола с реальным опытом отладки в браузере в три часа ночи.
В чём разница между REST и GraphQL
REST организует API вокруг ресурсов и стандартных HTTP-методов, каждый ресурс живёт на своём URL, а клиент обычно получает от сервера фиксированный набор полей, который сервер решил вернуть. GraphQL описывает единую схему данных и позволяет клиенту в одном запросе явно перечислить, какие именно поля ему нужны, ни больше ни меньше, что решает проблему избыточных данных и нехватки данных за один запрос, характерную для REST. Плата за гибкость GraphQL, кэширование на уровне HTTP становится сложнее, потому что запросы обычно идут через один и тот же эндпоинт методом POST, а не через понятные URL, которые легко кэшировать напрямую. GraphQL хорошо заходит там, где клиентов много и у них разные потребности в данных, REST остаётся проще и понятнее для типичного CRUD-сервиса с одним клиентом.
Что такое идемпотентность, почему PUT идемпотентен, а POST нет
Идемпотентный метод даёт один и тот же результат на сервере независимо от того, выполнили его один раз или пять раз подряд. PUT идемпотентен, потому что полностью заменяет ресурс по конкретному адресу заданными данными, повторный вызов с теми же данными просто перезапишет тот же ресурс тем же содержимым, ничего нового не создастся. POST не идемпотентен, потому что по своей природе создаёт новый ресурс, повторная отправка одной и той же формы, классический случай, повторная оплата из-за двойного клика по кнопке, обычно создаёт второй, дублирующий заказ. Отсюда практическая рекомендация, если нужна защита от повторов сетевого запроса на уровне клиента, это либо честный PUT, либо POST со специальным ключом идемпотентности, который сервер запоминает и не даёт выполнить операцию дважды.
Как работает CORS, почему браузер блокирует кросс-доменные запросы
Same-origin policy браузера по умолчанию не даёт скрипту с одного домена свободно читать ответы от запросов к другому домену, это базовая защита от вредоносного сайта, который иначе мог бы от имени жертвы дёргать API её банка, пока жертва просто открыла страницу с котиками. CORS это механизм, которым сервер явно разрешает конкретным доменам обращаться к себе, через заголовки вроде Access-Control-Allow-Origin. Для не самых простых запросов браузер сначала отправляет предварительный запрос OPTIONS, спрашивая разрешения у сервера, и только получив положительный ответ, отправляет настоящий запрос. Частая ошибка разработчиков, поставить Access-Control-Allow-Origin в звёздочку на проде ради удобства при разработке, что фактически снимает защиту same-origin policy для всех доменов сразу.
Чем session-based аутентификация отличается от JWT
При session-based подходе сервер после входа создаёт сессию и хранит её состояние у себя, а клиенту отдаёт только идентификатор сессии в куке, сервер при каждом запросе смотрит в своё хранилище и проверяет, что сессия существует и валидна. JWT устроен иначе, весь набор данных о пользователе и правах зашит прямо в сам токен и подписан секретным ключом сервера, поэтому серверу не нужно ничего хранить и запрашивать, достаточно проверить подпись токена. Отсюда главное преимущество JWT, простое горизонтальное масштабирование без общего хранилища сессий между серверами, и главный недостаток, отозвать конкретный JWT до истечения срока его действия сложно, ведь по самой конструкции токена сервер о нём ничего не помнит. На практике многие системы используют гибрид, короткоживущий JWT плюс отзываемый refresh-токен, хранящийся на сервере.
Что такое SSR, зачем он нужен, если есть SPA
SPA отдаёт браузеру почти пустой HTML и JavaScript-бандл, а весь реальный контент рендерится уже в браузере пользователя после загрузки и выполнения скриптов. Server-side rendering рендерит готовую HTML-страницу с контентом сразу на сервере и отдаёт её браузеру уже заполненной, JavaScript догружается и оживляет страницу интерактивностью следом. SSR решает две проблемы чистого SPA. Поисковым роботам и соцсетям, которые готовят превью ссылки, куда проще индексировать готовый HTML, чем ждать выполнения JavaScript. И пользователь на медленном соединении видит первый осмысленный контент заметно быстрее, потому что не ждёт, пока весь JS-бандл скачается и выполнится. Фреймворки вроде Next.js специально созданы, чтобы можно было выбирать SSR, статическую генерацию или чистый клиентский рендеринг для разных страниц одного и того же приложения.
В чём разница между localStorage, sessionStorage и cookie
localStorage хранит данные в браузере без срока действия, пока пользователь или скрипт явно их не удалит, и данные доступны только со стороны JavaScript, на сервер автоматически не отправляются. sessionStorage работает так же, но данные живут только пока открыта конкретная вкладка. Cookie отличается принципиально, во-первых у неё можно задать срок жизни, во-вторых куки автоматически прикладываются к каждому HTTP-запросу на соответствующий домен, что удобно для аутентификации, но и создаёт риски вроде CSRF. Токены аутентификации по рекомендациям OWASP лучше хранить в httpOnly cookie, а не в localStorage, потому что httpOnly делает куку недоступной для JavaScript и, соответственно, недосягаемой для XSS-атаки, тогда как всё, что лежит в localStorage, украдёт любой внедрённый скрипт.
Что такое rate limiting, какие алгоритмы используют
Rate limiting ограничивает число запросов от одного клиента за определённый промежуток времени, защищая сервис от перегрузки и от злоупотреблений вроде подбора пароля перебором. Token bucket выдаёт клиенту воображаемое ведро токенов, которое пополняется с фиксированной скоростью, каждый запрос тратит один токен, и если ведро пустое, запрос отклоняется, зато алгоритм спокойно переживает короткие всплески трафика, если токены накопились заранее. Leaky bucket работает наоборот, запросы складываются в очередь фиксированного размера и обрабатываются с постоянной скоростью, всплески сглаживаются, но не пропускаются мгновенно. API крупных сервисов обычно используют вариант token bucket и честно возвращают в заголовках ответа, сколько запросов осталось и когда лимит обнулится, чтобы клиент мог сам подстроиться, а не гадать.
Как работает HTTP-кэширование, зачем нужен CDN
Заголовок Cache-Control указывает браузеру и промежуточным прокси, можно ли кэшировать ответ и на какой срок. ETag это своего рода отпечаток содержимого файла, при повторном запросе браузер присылает сохранённый ETag, а сервер, если содержимое не изменилось, отвечает коротким статусом 304 Not Modified вместо повторной передачи всего файла целиком. CDN размещает копии статического контента на серверах, географически близких к пользователю, вместо того чтобы гонять каждый запрос до исходного сервера через полмира. Пользователь в Амстердаме, запрашивающий картинку с сайта, чей исходный сервер стоит где-то в Сингапуре, получит её с ближайшего узла CDN за миллисекунды, а не за те сотни миллисекунд, которые ушли бы на путешествие пакета через половину планеты и обратно.
Что такое вебхук, чем отличается от polling
Polling заставляет клиента самому регулярно спрашивать сервер, не появилось ли что-то новое, скажем каждые пять секунд дёргать эндпоинт и проверять статус заказа. Это просто в реализации, но неэффективно, большая часть запросов возвращает «ничего нового», а сервер всё равно тратит ресурсы на их обработку. Вебхук переворачивает модель, клиент один раз регистрирует у сервера собственный URL, и сервер сам, по факту наступления события, отправляет на этот URL HTTP-запрос с данными. Плата за эффективность, клиенту нужен собственный публично доступный эндпоинт, готовый принять входящий запрос в любой момент, и нужно продумать, что делать, если вебхук не долетел, обычно это повторные попытки доставки с экспоненциальной задержкой на стороне отправителя.
Опишите жизненный цикл HTTP-запроса через API Gateway в микросервисной архитектуре
Клиент отправляет запрос не напрямую к конкретному микросервису, а к единой точке входа, API Gateway. Gateway сначала проверяет аутентификацию, обычно валидирует JWT-токен, и применяет rate limiting для этого клиента. Дальше, по правилам маршрутизации, запрос направляется к нужному внутреннему сервису, скажем /orders идёт к сервису заказов, а /users к сервису пользователей. По пути gateway может логировать запрос, собирать метрики для трейсинга, а иногда и агрегировать ответы сразу нескольких сервисов в один, если это нужно конкретному клиенту. Клиенту не нужно знать ни внутреннюю структуру системы, ни адреса конкретных сервисов, ни то, что один сервис написан на Go, а соседний на Java, gateway прячет всю эту внутреннюю кухню за одним стабильным адресом.
Системный дизайн и архитектура
Седьмой блок обычно приберегают для старших позиций, но всё чаще спрашивают и специалистов среднего уровня, хотя бы в упрощённом виде. Правильного ответа здесь, как правило, нет вообще, собеседующего интересует ход рассуждений и явное проговаривание компромиссов.
Как бы вы спроектировали сокращатель ссылок вроде bit.ly
Основная задача, сгенерировать короткий уникальный код для каждой длинной ссылки и быстро отдавать редирект по этому коду. Код можно получить, закодировав автоинкрементный числовой идентификатор записи в системе счисления с основанием 62, это гарантирует уникальность без дополнительных проверок на совпадение. Хранилище, по сути, простая таблица «короткий код, оригинальный URL», но нагрузка на чтение обычно на порядки выше нагрузки на запись, значит имеет смысл держать самые популярные ссылки в кэше перед базой. Дальше начинаются уточняющие вопросы, нужна ли статистика переходов, нужен ли срок жизни ссылки, сколько переходов в секунду ожидается, и тут собеседующему обычно важнее не финальная архитектура, а то, что кандидат сам эти вопросы задаёт, а не сразу рисует диаграмму.
В чём разница между монолитом и микросервисами, когда монолит лучше
Монолит собирает всю бизнес-логику приложения в одном развёртываемом процессе с общей базой данных. Микросервисы разбивают систему на независимые сервисы, каждый со своей зоной ответственности, часто со своей базой данных, взаимодействующие по сети. Микросервисы обещают независимое масштабирование, независимые релизы и изоляцию отказов, но платят за это распределённой сложностью, сетевые вызовы вместо вызовов функций, необходимость следить за согласованностью данных между сервисами, отдельная система мониторинга и трейсинга. Для стартапа с командой из пяти разработчиков и неопределённым продуктом монолит почти всегда правильнее, микросервисная архитектура добавляет операционные накладные расходы, которые окупаются только при определённом масштабе команды и нагрузки. Мартин Фаулер в своё время предлагал шутливое, но полезное правило, начинайте с монолита и режьте его на сервисы, когда границы предметной области станут понятны на практике, а не заранее по учебнику.
Что такое CAP-теорема
Теорема, сформулированная Эриком Брюером в 2000 году, утверждает, что распределённая система при разрыве сети может гарантировать одновременно только два из трёх свойств, согласованность, доступность и устойчивость к разделению сети. Поскольку разрывы сети в реальном распределённом мире рано или поздно случаются неизбежно, устойчивость к разделению по факту не опция, а данность, значит настоящий практический выбор идёт между согласованностью и доступностью именно в момент разрыва связи между узлами. Банковская система при разрыве скорее откажет в обслуживании, чем покажет неверный баланс счёта, а лента соцсети скорее покажет чуть устаревший список лайков, чем откажется загружаться вовсе. Ошибка, которую любят на собеседовании ловить, теорема касается именно поведения при разрыве сети, а не общего компромисса системы в спокойном состоянии.
Как спроектировать систему уведомлений для миллионов пользователей
Систему разумно разделить на приём событий и фактическую доставку. Событие попадает в очередь сообщений вместо того, чтобы сразу пытаться разослать уведомление синхронно, это защищает основной сервис от скачков нагрузки и от медленных внешних провайдеров. Дальше отдельные воркеры разбирают очередь и решают, каким каналом отправить конкретное уведомление, push через APNs или FCM, email через SendGrid, SMS через отдельного провайдера, у каждого канала свой лимит скорости и своя вероятность временного отказа. Разумно сразу заложить дедупликацию, чтобы не отправить одно и то же уведомление дважды при повторной обработке очереди, и приоритизацию, критичное уведомление о подозрительном входе в аккаунт должно уйти раньше, чем рассылка про скидку выходного дня, даже если оба события оказались в очереди почти одновременно.
Что такое очередь сообщений, почему Kafka предпочитают RabbitMQ в высоконагруженных системах
Очередь сообщений развязывает производителя и потребителя данных во времени, производитель кладёт сообщение в очередь и не ждёт, пока потребитель его обработает. RabbitMQ традиционно устроен вокруг модели брокера, который активно распределяет сообщения между потребителями и удаляет их после подтверждения обработки, хорошо подходит для классических очередей задач с гибкой маршрутизацией. Kafka устроен иначе, это по сути распределённый неизменяемый журнал, сообщения не удаляются сразу после чтения, а хранятся заданное время, и несколько независимых групп потребителей могут читать один и тот же поток данных с разной скоростью и с разных мест. Именно лог вместо очереди и репликация партиций между брокерами дают Kafka пропускную способность в миллионы сообщений в секунду на кластер и делают его основным выбором для стриминга событий и логов в компаниях уровня LinkedIn, где Kafka изначально и родился в 2011 году.
Как бы вы спроектировали ленту новостей в соцсети
Есть два базовых подхода. Fan-out on write генерирует персональную ленту заранее, в момент публикации поста система сразу проталкивает его во все ленты подписчиков автора и складывает результат в кэш, а чтение ленты потом мгновенное. Fan-out on read, наоборот, откладывает всю работу до момента открытия ленты, читатель заходит, и система на лету собирает посты всех, на кого он подписан, сортирует по времени и отдаёт. Первый подход быстрый на чтение, но дорогой на запись и плохо масштабируется для аккаунтов с миллионами подписчиков, представьте, что один пост знаменитости нужно мгновенно протолкнуть в сто миллионов лент. Второй подход дешевле на запись, но каждое открытие ленты требует немаленькой работы на лету. Крупные соцсети в реальности используют гибрид, fan-out on write для обычных пользователей и fan-out on read именно для аккаунтов с огромным числом подписчиков.
Что такое eventual consistency, где она приемлема
Eventual consistency гарантирует, что если новых записей больше не поступает, все реплики системы рано или поздно придут к одному и тому же состоянию, но в моменте разные узлы могут временно отдавать разные ответы на один и тот же запрос. Это приемлемо там, где секундная задержка в видимости данных не критична, счётчик лайков, который на секунду отстаёт на одном из серверов, никого не разорит и не подорвёт доверие к сервису. Это неприемлемо там, где несогласованность стоит реальных денег или доверия, баланс банковского счёта или наличие товара на складе в момент оформления заказа должны быть согласованы строго. Отсюда практическое правило, для каждого конкретного куска данных стоит осознанно решить, действительно ли нужна строгая согласованность и стоит ли платить за неё задержкой, или достаточно eventual consistency ради скорости и доступности.
Как масштабировать базу данных при росте нагрузки
Вертикальное масштабирование, самое простое, добавить более мощное железо на тот же единственный сервер, больше памяти, быстрее диск, больше ядер. Просто в реализации, но упирается в физический потолок одной машины и рано или поздно становится неоправданно дорогим. Горизонтальное масштабирование распределяет нагрузку между несколькими серверами. Для чтения это обычно реплики, на которые направляют запросы SELECT, оставляя запись на одном основном сервере. Для записи всё сложнее, и тут либо шардирование, либо переход на распределённую базу, изначально спроектированную под горизонтальную запись, вроде CockroachDB или Cassandra. Правильная последовательность на практике почти всегда такая, сначала выжать вертикальное масштабирование и оптимизацию запросов и индексов, и только когда это физически упёрлось в потолок, переходить к горизонтальному масштабированию с его несоизмеримо большей операционной сложностью.
Что такое паттерн circuit breaker, зачем он нужен
В микросервисной архитектуре один медленный или упавший сервис легко утаскивает за собой остальные, если вызывающий сервис продолжает слать запросы и ждать таймаута на каждый из них, исчерпывая собственные потоки и соединения в ожидании ответа, который никогда не придёт. Circuit breaker работает как электрический предохранитель, отслеживает долю неудачных запросов к конкретному сервису, и если она превышает порог, размыкается и начинает сразу отдавать ошибку без реальной попытки дозвониться до упавшего сервиса, пока не пройдёт время на восстановление. Через какое-то время breaker переходит в полуоткрытое состояние и пробует пропустить несколько тестовых запросов, если они успешны, полностью восстанавливает нормальную работу, если нет, снова размыкается. Библиотека Hystrix от Netflix в своё время популяризировала этот паттерн в микросервисном мире, хотя сама библиотека уже официально в режиме поддержки, и сегодня чаще используют её идейных последователей вроде resilience4j.
Как спроектировать rate-limiting API с учётом отказоустойчивости
Наивная реализация хранит счётчик запросов в памяти каждого отдельного сервера, но при нескольких серверах за балансировщиком это ломается, клиент может обойти лимит, просто попадая на разные серверы по очереди. Правильный подход хранит общий счётчик в централизованном быстром хранилище, обычно Redis, доступном всем серверам сразу, с атомарными операциями инкремента и с истечением ключа по времени. Проблема этого решения в том, что Redis становится единой точкой отказа для всего rate limiting, а значит его самого нужно разворачивать в отказоустойчивой конфигурации, и продумать, что произойдёт с системой, если само хранилище лимитов временно недоступно. Разумный компромисс на такой случай, fail open вместо fail closed, если Redis недоступен, лучше временно пропустить чуть больше запросов, чем нужно, чем полностью остановить сервис из-за упавшей вспомогательной инфраструктуры для защиты от перегрузки.
DevOps, контейнеры и облака
Восьмой блок за последние пару лет разросся сильнее остальных, а после 19 июля 2024 года в него добавился негласный подраздел про то, что бывает, если раскатывать обновления без канареечного релиза.
Чем контейнер отличается от виртуальной машины
Виртуальная машина эмулирует целиком отдельное железо и запускает поверх него полноценную гостевую операционную систему со своим ядром, гипервизор управляет несколькими такими машинами на одном физическом сервере. Контейнер не эмулирует железо и не запускает отдельное ядро, он использует ядро операционной системы хоста, но изолирует процессы друг от друга через механизмы самого ядра Linux, namespaces для изоляции, cgroups для ограничения ресурсов. Отсюда контейнер стартует за доли секунды и весит мегабайты, а виртуальная машина стартует за десятки секунд или минуты и весит гигабайты, потому что тащит с собой целую операционную систему. Плата за лёгкость контейнера, изоляция слабее, чем у полноценной виртуальной машины, побег из контейнера на хостовую систему теоретически возможен через уязвимости ядра, тогда как побег из виртуальной машины требует пробить границу самого гипервизора, что заметно сложнее.
Что такое Kubernetes Pod, почему это не просто контейнер с другим именем
Под это минимальная единица развёртывания в Kubernetes, и внутри одного пода может жить не один, а несколько контейнеров, которые делят общее сетевое пространство и опционально общие тома для файлов. Обычно это один основной контейнер с приложением плюс один или несколько вспомогательных, например контейнер, который собирает логи основного приложения и отправляет их во внешнюю систему, или прокси вроде Envoy в service mesh, который перехватывает весь сетевой трафик пода. Все контейнеры одного пода всегда живут и умирают вместе, планируются на одну и ту же физическую ноду и никогда не разносятся Kubernetes по разным серверам. Именно возможность нескольких тесно связанных контейнеров с общей сетью и общим жизненным циклом и отличает под от голого запуска одного контейнера.
Как работает CI/CD пайплайн, чем continuous delivery отличается от continuous deployment
CI означает, что каждое изменение кода автоматически собирается и прогоняется через тесты сразу после отправки в общий репозиторий, вместо того чтобы копить изменения неделями и потом мучительно их сливать разом. Continuous delivery идёт дальше, каждое изменение, прошедшее тесты, автоматически доводится до состояния «готово к выкладке в прод», но финальное нажатие кнопки развёртывания остаётся за человеком. Continuous deployment убирает и эту последнюю ручную кнопку, любое изменение, прошедшее весь конвейер проверок, автоматически оказывается в проде без вмешательства человека вообще. Разница между delivery и deployment не техническая, а организационная, вопрос не в том, может ли команда автоматически развернуть новую версию, а в том, готова ли она психологически довериться конвейеру тестов настолько, чтобы убрать последнюю кнопку подтверждения из процесса.
Что такое Infrastructure as Code, почему Terraform предпочитают ручной настройке
Infrastructure as Code описывает серверы, сети и прочую инфраструктуру декларативным кодом в файлах, а не набором ручных кликов в консоли облачного провайдера. Terraform читает такое описание и сам вычисляет, что нужно создать, изменить или удалить, чтобы реальная инфраструктура пришла в соответствие с описанным состоянием. Преимущество перед ручной настройкой очевидно при третьей попытке развернуть окружение, код можно версионировать в Git, проверять как обычный pull request, воспроизводимо прогонять в разных окружениях без риска, что кто-то в проде забыл один чекбокс, который выставлял вручную месяц назад в тестовом контуре. Ручная настройка через консоль хорошо работает для одного эксперимента на выходных, но плохо масштабируется на команду из десяти инженеров, которые правят одну и ту же инфраструктуру в разное время суток без единого источника правды.
Чем blue-green развёртывание отличается от canary-релиза
Blue-green держит два полностью идентичных окружения, старую версию и новую, новую версию разворачивают полностью и тестируют отдельно от боевого трафика, а затем переключают балансировщик разом со старой версии на новую. Откат при проблеме мгновенный, просто переключить балансировщик обратно. Canary-релиз, наоборот, направляет на новую версию сначала небольшую долю реального трафика, скажем пять процентов пользователей, и постепенно увеличивает эту долю, если метрики остаются в норме. Canary снижает риск, потому что ошибка в новой версии затронет сразу малую часть пользователей, а не всех разом, но требует более сложной инфраструктуры маршрутизации трафика и терпения, полный раскат может занять часы вместо мгновенного переключения. CrowdStrike в июле 2024 года разослал проблемное обновление сразу на все хосты без всякого канареечного раската, и цена этой экономии на постепенности вышла в миллиарды долларов совокупного ущерба по всему миру.
Что делает Dockerfile, почему важен порядок инструкций
Dockerfile это набор инструкций, по которым Docker собирает образ, и каждая инструкция создаёт отдельный слой поверх предыдущего, причём слои кэшируются. Если конкретный слой и все инструкции до него не изменились с прошлой сборки, Docker просто переиспользует закэшированный слой вместо повторного выполнения. Отсюда практическое правило порядка, инструкции, которые меняются редко, установка системных зависимостей, копирование файла со списком библиотек, стоит ставить раньше в файле, а команду, копирующую весь исходный код проекта, который меняется на каждый коммит, ставить как можно позже. Нарушение этого порядка, скажем, копирование всего кода первой строчкой, а установка зависимостей второй, приводит к тому, что любое изменение одной строчки в коде сбрасывает кэш зависимостей, и каждая сборка заново скачивает и устанавливает все библиотеки, что на большом проекте превращает тридцатисекундную сборку в пятиминутную.
Как работает horizontal pod autoscaler в Kubernetes
HPA следит за метриками подов, по умолчанию за загрузкой процессора, но можно настроить и на кастомные метрики вроде длины очереди задач, и автоматически меняет число реплик пода, чтобы держать среднюю загрузку около заданного целевого значения. Если средняя загрузка процессора превышает, скажем, семьдесят процентов от заданного лимита, HPA поднимает дополнительные копии пода, распределяя нагрузку на большее число экземпляров, и наоборот, снижает их число, когда нагрузка спадает. Ключевая деталь, HPA масштабирует число подов, а не ресурсы внутри одного пода, это горизонтальное масштабирование в чистом виде, для вертикального в Kubernetes есть отдельный, куда менее популярный механизм, Vertical Pod Autoscaler. На практике HPA настраивают с запасом по времени реакции, потому что поднять новый под и дождаться его готовности занимает секунды, а всплеск трафика может случиться мгновенно.
Что такое observability, чем отличается от мониторинга
Классический мониторинг отвечает на заранее известные вопросы через заранее настроенные дашборды и алерты, скажем, «нагрузка на процессор выше девяноста процентов», что удобно для известных проблем, но бессильно перед незнакомой ситуацией, которую никто заранее не предугадал. Observability строится на трёх столпах, логи, метрики и трейсы, путь конкретного запроса через все микросервисы, которые он затронул, и вместе они дают возможность задавать новые вопросы к уже собранным данным постфактум, а не только смотреть на заранее подготовленные графики. Разница практическая, мониторинг скажет, что сервис отвечает медленно, observability, при достаточной глубине трейсинга, покажет, что конкретно этот медленный ответ на девяносто процентов состоял из ожидания одного конкретного медленного вызова к внешнему API, а не из работы самого сервиса. Инструменты вроде Jaeger, Grafana и связка Prometheus плюс Loki обычно и покрывают эти три столпа вместе.
Что происходит при падении пода в Kubernetes, чем это отличается от падения контейнера без оркестратора
Kubelet на ноде постоянно следит за состоянием подов через liveness и readiness probes, и если под перестаёт отвечать на проверку живости, kubelet его перезапускает автоматически, без вмешательства человека. Контроллер более высокого уровня следит уже не за конкретным подом, а за тем, что нужное число реплик существует в принципе, и если под упал полностью и не поднимается на той же ноде, контроллер закажет новый под на другой доступной ноде. Без оркестратора, при простом запуске контейнера на одном сервере, его падение означает простой до тех пор, пока кто-то не заметит проблему и не перезапустит его вручную или через отдельно настроенный systemd с политикой перезапуска. Разница именно в том, что Kubernetes воспринимает желаемое состояние как постоянную декларативную цель и непрерывно сверяет с ней реальность, а не реагирует на конкретное разовое падение постфактум.
Что такое GitOps, чем отличается от классического CI/CD
Классический CI/CD обычно работает по push-модели, пайплайн после успешной сборки сам подключается к продовому кластеру и применяет изменения, у пайплайна должны быть широкие права доступа прямо в боевую инфраструктуру. GitOps переворачивает направление, единственным источником истины становится Git-репозиторий с описанием желаемого состояния инфраструктуры, а специальный агент внутри самого кластера, ArgoCD или Flux, сам постоянно сверяет реальное состояние с тем, что записано в репозитории, и подтягивает изменения, если они разошлись, работая по pull-модели изнутри, а не получая команды снаружи. Преимущество, у внешнего CI-пайплайна больше не нужны прямые ключи доступа к продовому кластеру, что заметно сужает поверхность атаки, а любое изменение инфраструктуры автоматически имеет полную историю в Git с указанием автора и причины. Побочный эффект, откат неудачного изменения превращается в обычный git revert, а не в судорожный поиск, что именно поменяли в проде в последний раз.
Языки программирования и отладка
Девятый блок отличается сильнее всего от компании к компании, но принципы, которые за конкретным синтаксисом стоят, почти всегда одни и те же. По данным TIOBE, Python в 2026 году устойчиво держит первое место, в июньском рейтинге его доля превышает двадцать процентов, но вопросы ниже актуальны и для тех, кто на собеседовании обсуждает Java, JavaScript или Go.
В чём разница между статической и динамической типизацией
При статической типизации тип переменной проверяется на этапе компиляции, до запуска программы, и часть ошибок отлавливается ещё до того, как код вообще исполнился хоть раз. При динамической типизации тип определяется во время выполнения программы, переменная может в разные моменты содержать значения разных типов, а ошибка несовместимости типов вылезет только тогда, когда конкретная строчка кода реально исполнится. Статическая типизация даёт больше уверенности на больших командах и в больших кодовых базах, потому что компилятор сам ловит целый класс ошибок и рефакторинг становится безопаснее. Динамическая типизация даёт скорость написания прототипа и меньше формального шума в коде, платя за это тем, что часть ошибок типов всплывает уже в проде, а не на этапе сборки.
Что такое сборка мусора, почему в Rust её нет
Сборщик мусора автоматически находит и освобождает память, на которую больше никто не ссылается в программе, избавляя разработчика от ручного управления памятью, но добавляя накладные расходы, время от времени сборщик приостанавливает выполнение программы, чтобы пройтись по памяти и найти ненужное. Rust решает проблему управления памятью иначе, без сборщика мусора вообще, через систему владения и заимствования, которую компилятор проверяет статически на этапе компиляции, и если правила нарушены, код просто не скомпилируется. Ровно один владелец у каждого значения памяти в любой момент времени, и когда владелец выходит из области видимости, память освобождается детерминированно и мгновенно, без пауз сборщика где-либо в рантайме. Цена такого подхода, компилятор Rust заметно строже и придирчивее большинства языков, и первые недели изучения часто уходят на споры с borrow checker, зато результат работает предсказуемо быстро без скрытых пауз.
Чем передача по значению отличается от передачи по ссылке
При передаче по значению функция получает копию данных, и любые изменения внутри функции никак не затрагивают оригинал в вызывающем коде. При передаче по ссылке функция получает не копию, а прямой доступ к тому же самому месту в памяти, и изменения внутри функции видны и снаружи, после её завершения. Путаница чаще всего возникает в языках вроде Python или Java, где формально всё передаётся по значению, но это значение для объектов представляет собой ссылку на объект, поэтому изменение содержимого объекта внутри функции видно снаружи, а вот переприсваивание самой переменной новому объекту внутри функции снаружи не видно. Отсюда классическая ловушка на собеседовании, список, переданный в функцию, можно изменить изнутри функции методом append, и это изменение сохранится, но если внутри функции переменной присвоить совершенно новый список, снаружи по-прежнему будет виден старый.
Что такое замыкание в JavaScript
Замыкание возникает, когда внутренняя функция запоминает и сохраняет доступ к переменным из области видимости внешней функции, даже после того как внешняя функция уже завершила выполнение. Классический случай, функция-счётчик, которая создаёт локальную переменную и возвращает внутреннюю функцию, увеличивающую эту переменную на единицу при каждом вызове, каждый вызов внешней функции создаёт независимый счётчик со своим собственным состоянием, недоступным снаружи напрямую. Замыкания используют для инкапсуляции приватного состояния там, где в языке нет полноценных приватных полей класса, для создания фабрик функций с частично зафиксированными параметрами, и повсеместно в обработчиках событий, которым нужно помнить контекст на момент их создания. Обратная сторона, забытые ссылки в замыканиях исторически были частой причиной утечек памяти в старых версиях браузеров, хотя современные движки JavaScript с этим справляются заметно лучше.
В чём разница между == и === в JavaScript
Оператор == сравнивает значения после приведения типов, если типы разные, JavaScript сначала попробует их привести к общему типу и только потом сравнить, отсюда странности вроде того, что ноль равен пустой строке, а строка «0» равна числу 0 согласно ==. Оператор === сравнивает и значение, и тип одновременно, без всякого неявного приведения, и если типы разные, результат сразу false. Именно из-за неочевидных и местами абсурдных правил приведения типов при == большинство современных гайдлайнов по стилю кода, включая ESLint по умолчанию, требуют всегда использовать строгое сравнение ===, оставляя == только для тех редких случаев, где неявное приведение типов действительно осознанно нужно.
Что такое race condition, как его найти в многопоточном коде
Race condition возникает, когда результат работы программы зависит от непредсказуемого порядка выполнения нескольких потоков, обращающихся к общим данным без должной синхронизации, и один и тот же код может в одном запуске отработать правильно, а в другом дать неверный результат просто из-за другого расклада по времени. Классический случай, два потока одновременно читают значение счётчика, оба увеличивают прочитанное значение на единицу локально и оба записывают результат обратно, из-за гонки итоговое значение увеличивается только на единицу вместо ожидаемых двух. Ловить такие ошибки отладчиком тяжело именно потому, что сам факт присоединения отладчика к процессу меняет тайминги выполнения потоков и часто маскирует проблему. Практический подход, статические анализаторы гонок данных, в Go race detector встроен прямо в компилятор через флаг -race, и явная синхронизация через мьютексы или атомарные операции там, где потоки трогают общие данные.
Чем компилируемый язык отличается от интерпретируемого, куда отнести Python
Компилируемый язык переводится в машинный код заранее, до запуска, и результатом становится готовый исполняемый файл, который процессор выполняет напрямую без посредников. Интерпретируемый язык в классическом понимании выполняется построчно специальной программой-интерпретатором прямо во время запуска, без отдельного этапа компиляции в машинный код. Python на практике живёт где-то посередине, исходный код сначала компилируется в промежуточный байт-код, а уже байт-код исполняется виртуальной машиной CPython, что структурно похоже на подход Java с её JVM, хотя Java исторически принято называть компилируемым языком, а Python интерпретируемым, это скорее вопрос устоявшейся терминологии, чем строгой технической границы. JIT-компиляция, которую использует V8 в браузерах для JavaScript, добавляет ещё один слой размытия этой границы, компилируя в машинный код на лету самые часто вызываемые участки кода.
Что такое null pointer exception, почему Тони Хоар назвал null ошибкой на миллиард долларов
Null pointer exception возникает, когда код пытается обратиться к полю или методу переменной, которая на самом деле не указывает ни на какой реальный объект, а хранит специальное значение null. Тони Хоар, придумавший конструкцию null-ссылок в языке ALGOL W ещё в 1965 году, публично признал в 2009-м, что это решение обошлось индустрии примерно в миллиард долларов ущерба за прошедшие десятилетия, именно из-за бесчисленных падений программ на пустой ссылке, которую забыли проверить перед использованием. Современные языки всё чаще стараются убрать саму возможность такой ошибки на уровне системы типов, а не полагаться на дисциплину разработчика, Kotlin и Swift различают nullable и non-nullable типы прямо в объявлении переменной, а Rust вместо null использует тип Option, который явно заставляет обработать оба случая, есть значение или нет, прежде чем код вообще скомпилируется. Полностью исчезнуть null pointer exception из индустрии пока не готов, слишком много кода написано на языках, где null остаётся полноправным гражданином.
Как отладить утечку памяти в приложении на Java
Первый шаг обычно, снять heap dump, снимок всего содержимого кучи в конкретный момент времени, штатной утилитой jmap или через флаг автоматического дампа при OutOfMemoryError. Дальше дамп открывают в анализаторе и смотрят, какие объекты занимают больше всего памяти и, что важнее, кто на них до сих пор держит ссылки, не давая сборщику мусора их забрать. Частые виновники утечек в Java, статические коллекции, в которые объекты добавляют, но никогда не удаляют, слушатели событий, которые зарегистрировали, но забыли отписать при уничтожении объекта, и кэши без ограничения размера и без вытеснения старых записей. Хороший диагностический приём, снять два heap dump с разницей в час-два под одинаковой нагрузкой и сравнить, какие типы объектов выросли количественно сильнее остальных, именно такой рост почти всегда и указывает прямо на источник утечки.
Что такое рекурсия, чем отличается от циклов с точки зрения стека
Рекурсивная функция вызывает саму себя с изменённым набором аргументов, пока не достигнет базового случая, на котором рекурсия останавливается и начинается разворачивание всех накопленных вызовов обратно. Цикл решает похожие задачи итеративно, повторяя один и тот же блок кода без дополнительных вызовов функции. Разница, критичная на практике, каждый рекурсивный вызов добавляет новый кадр в стек вызовов, и при слишком глубокой рекурсии программа упирается в переполнение стека, тогда как цикл использует ровно один и тот же кадр стека независимо от числа итераций. Некоторые языки умеют оптимизировать хвостовую рекурсию, превращая её в обычный цикл на уровне компилятора и убирая риск переполнения стека, но ни Python, ни Java такой оптимизации по умолчанию не делают, и глубокая рекурсия там остаётся реальным риском, а не только теоретическим.
Поведенческие и ситуационные вопросы в IT
Последний блок раздражает многих технарей больше, чем любая задача про графы, и совершенно зря. Собеседующему тут интересен не красивый рассказ, а конкретика, что именно кандидат сделал сам, а не что предприняла команда в целом.
Расскажите о случае, когда вы нашли ошибку в продакшене
Хороший ответ построен по структуре STAR, ситуация, задача, действие, результат, а не абстрактным рассуждением о важности мониторинга. Слабый ответ звучит примерно так, «мы заметили ошибку и быстро её исправили». Сильный ответ называет конкретику, какая именно метрика или алерт указал на проблему, сколько пользователей она реально затронула, что конкретно сделал именно кандидат, откатил релиз, написал срочное исправление, поднял инцидент в системе трекинга, и чем всё закончилось, включая честное признание, если решение оказалось временным костылём, а не идеальным фиксом. Отдельный плюс в глазах собеседующего, если рассказ включает разбор после инцидента без поиска виноватого, который команда провела после того, как пожар потушили, и что именно изменили в процессе, чтобы такой же класс ошибок не повторился.
Как вы реагируете, когда не согласны с техническим решением тимлида
Ожидаемый ответ не про молчаливое согласие и не про открытый бунт, а про конкретный механизм несогласия. Сначала стоит убедиться, что несогласие основано на полной картине, возможно у тимлида есть контекст, сроки, договорённости с другими командами, прошлый неудачный опыт с похожим решением, которого у кандидата нет. Дальше уместно один раз чётко и с аргументами высказать альтернативную позицию, желательно письменно и с конкретными техническими доводами, а не общими сомнениями. Если решение всё равно принято не в пользу кандидата, взрослая позиция, зафиксировать несогласие явно, но добросовестно реализовать принятое решение, а не саботировать его исполнение молча. Хороший рассказчик на этом вопросе обычно приводит реальный пример с конкретным техническим спором, а не абстрактную философию о культуре взаимоуважения в команде.
Опишите ситуацию, когда дедлайн был нереалистичным
Сильный ответ показывает не героическую переработку по ночам, а разумную работу с ожиданиями заранее. Хороший подход, при первых признаках, что срок не выдержать, сообщить об этом как можно раньше, а не в последний день, и прийти не с жалобой, а с вариантами, что можно урезать в объёме функциональности, что можно сделать параллельно, а не последовательно, какая часть работы вообще не критична для конкретного дедлайна и может выйти позже отдельным релизом. Слабый ответ, которого стоит избегать на собеседовании, рассказ о том, как кандидат просто работал по выходным без сна, чтобы успеть, потому что такой ответ сигнализирует не героизм, а неумение вовремя эскалировать проблему и склонность к выгоранию, которую любой опытный менеджер распознаёт как будущий риск для команды, а не как достоинство.
Как вы объясните сложную техническую проблему нетехническому менеджеру
Работающий приём, начинать с последствий для бизнеса, а не с технических деталей причины. Не «у нас проблема N+1 в ORM», а «страница загружается три секунды вместо ожидаемых трёхсот миллисекунд, и это напрямую бьёт по конверсии оформления заказа». Дальше можно предложить бытовую аналогию, если она реально помогает, а не выдумывать её ради красоты, тот самый официант с пятью столиками, который держит внимание сразу на всех и не зависает ни на одном, годится для объяснения асинхронности, но для объяснения проблемы N+1 в базе данных лучше подойдёт пример похода в магазин, где вместо одного списка покупок пришлось бы возвращаться отдельно за каждым товаром. Важно не упрощать до искажения сути, менеджер должен понять достаточно, чтобы принять осмысленное решение о приоритете исправления, а не просто получить красивую метафору без практической пользы.
Расскажите о конфликте в команде и как его разрешили
Собеседующего интересует не факт конфликта, конфликты в любой живой команде неизбежны, а конкретная роль кандидата в его разрешении. Слабый рассказ перекладывает всю ответственность на другую сторону конфликта, «коллега постоянно опаздывал с задачами, и я просто терпел». Сильный рассказ показывает инициативу, кандидат сам инициировал прямой разговор один на один, а не жаловался руководителю за спиной коллеги, сформулировал проблему в терминах конкретного поведения и его последствий, а не личных качеств человека, и предложил конкретное решение, перераспределить задачи, чаще синхронизироваться, эскалировать конкретный процесс, а не человека. Хорошим завершением истории обычно служит не идеальный хэппи-энд, а честный вывод о том, что кандидат вынес из ситуации для себя на будущее.
Что делать, если код-ревью раскритиковали жёстко и, на ваш взгляд, несправедливо
Первая реакция, которую разумно подавить, оборонительный ответ прямо в комментариях под тем же ревью. Полезнее сначала честно отделить эмоциональную реакцию на тон комментария от технической сути замечания, иногда резкая формулировка скрывает вполне справедливое техническое возражение, а иногда действительно представляет собой придирку не по делу. Дальше уместно уточнить позицию рецензента напрямую, желательно голосом или видеозвонком, а не эскалацией переписки в текстовом канале, где тон легко читается неверно в обе стороны. Если после разговора замечание всё ещё кажется необоснованным, взрослая позиция, спокойно аргументировать свою позицию с техническими доводами, а не эмоциями, и принять, что иногда правым в итоге оказывается рецензент, а иногда автор кода, и оба варианта нормальны для рабочего процесса, а не повод для обиды на неделю.
Как вы расставляете приоритеты, когда одновременно горят три задачи
Работающий подход, для каждой из трёх задач честно оценить два параметра, реальное влияние на бизнес или пользователей и реальную срочность именно сейчас, а не субъективное ощущение важности того, кто громче всех просит. Задача, которая одновременно и важна, и срочна, идёт первой без обсуждений. Дальше стоит явно и вслух, а не молча в голове, сообщить всем трём заинтересованным сторонам, в каком порядке кандидат планирует закрывать задачи и почему именно так, это снимает большую часть недовольства заранее, потому что люди спокойнее реагируют на честную прозрачность приоритетов, чем на молчаливое исчезновение с их задачей на неопределённый срок. Хороший ответ также признаёт, что иногда правильный шаг, честно сказать руководителю, что три горящие задачи одновременно физически не решить одному человеку в разумный срок, вместо того чтобы героически распыляться на все три и не закрыть толком ни одну.
Опишите ситуацию, где вы ошиблись, и как признали ошибку перед командой
Собеседующий здесь скорее проверяет не сам факт ошибки, её отсутствие в ответе как раз подозрительно, ошибаются все, а способность признать её быстро и без попыток свалить вину на обстоятельства или коллег. Сильный рассказ содержит конкретику, что именно пошло не так технически, например развёртывание без проверки миграции базы данных уронило продакшен на двадцать минут, кто и как обнаружил проблему, и что кандидат сделал сразу после осознания своей ошибки, сообщил о ней открыто в команде, не дожидаясь, пока это обнаружит кто-то другой, помог с исправлением, а после уже спокойно разобрал с командой, что в процессе стоит поменять, чтобы такая ошибка физически не могла повториться. Рассказ, который заканчивается конкретным изменением процесса, а не просто извинением, воспринимается заметно убедительнее.
Как вы поддерживаете актуальность знаний в быстро меняющейся отрасли
Расплывчатый ответ вроде «читаю статьи и слежу за новостями» звучит одинаково у всех и ничего не говорит собеседующему. Конкретный ответ называет реальные источники и реальную периодичность, скажем, регулярное чтение конкретных технических блогов или профильных Telegram-каналов, участие в конкретных митапах или конференциях, эксперименты с новым инструментом на пет-проекте, а не только в теории. Честный и при этом рабочий подход, признать, что успеть выучить абсолютно всё физически невозможно, и объяснить, как кандидат выбирает, во что вкладывать время осознанно, ориентируясь на то, что реально требуется в текущей и ближайшей будущей работе, а не гоняясь за каждой новой технологией просто потому, что о ней вдруг заговорили все вокруг.
Почему вы хотите уйти с текущего места или выбрали именно эту компанию
Худший из возможных ответов ругает бывшего или текущего работодателя напрямую, даже если для этого есть все основания, потому что собеседующий слышит в такой жалобе не столько правду о прошлой компании, сколько сигнал о том, как кандидат будет говорить о нынешнем месте на следующем собеседовании через пару лет. Работающий ответ формулирует уход в терминах того, к чему кандидат стремится, а не от чего убегает, недостаточно интересных технических задач на текущем уровне, отсутствия роста в конкретном желаемом направлении, желания сменить масштаб компании в ту или другую сторону. Сильный ответ дополнительно связывает эти стремления с конкретикой именно той компании, куда кандидат сейчас пришёл на собеседование, а не превращается в одну и ту же универсальную заготовку, которую произносят на любом собеседовании подряд, независимо от того, кто сидит по другую сторону стола.
Сто вопросов выше не гарантируют оффер и не заменяют реальный опыт, который либо есть, либо его предстоит получить на первой же боевой инцидентной ночи. Зато они снимают самый глупый способ провалить собеседование, не потому что кандидат не умел решать задачу, а потому что никогда раньше не встречал её формулировку и растерялся на ровном месте. Рынок труда в 2026 году стал придирчивее, hh.индекс в отрасли не даёт соврать, но придирчивость собеседующего почти всегда конкретна и предсказуема, если знать, куда смотреть заранее. Дальше вопрос действительно только один, готов ли кандидат вслух рассуждать даже тогда, когда не уверен в ответе, потому что именно это, а не идеальная память на определения, отличает специалиста среднего уровня от старшего на собеседовании, где угодно.
