Кожне питання в банку це окремий файл з рівнем (junior, middle, senior), темою, набором реальних формулювань «як питають», критеріями «що хоче почути інтервʼюер», списком типових помилок і чотирма-пʼятьма follow-up питаннями з відповідями. Зараз карток 117. Усе, що нижче, пораховано по цих файлах, і кожне число можна перевірити самому на /interview-questions.
За рівнями: 36 junior, 52 middle, 29 senior. За темами: PHP 31, Laravel 24, SQL 14, архітектура 12, Symfony 11, WordPress 11, DevOps 9, Yii 5.
Жодна тема не зникає, змінюється рівень, на якому її беруть
Розклад тем за рівнями:
| Тема | junior | middle | senior |
|---|---|---|---|
| PHP | 14 | 12 | 5 |
| Laravel | 9 | 10 | 5 |
| SQL | 4 | 6 | 4 |
| Архітектура | 1 | 5 | 6 |
| Symfony | 2 | 6 | 3 |
| WordPress | 3 | 6 | 2 |
| DevOps | 2 | 4 | 3 |
| Yii | 1 | 3 | 1 |
Усі вісім тем мають картки на всіх трьох рівнях. Жодна не вмикається тільки на senior і не зникає після junior. Перекошені лише дві теми, причому в різні боки.
PHP як мова тримається знизу: 26 карток з 31 це junior або middle. На senior від мови лишається пʼять питань, і жодне з них не про синтаксис: OPcache і JIT, garbage collector, WeakMap і WeakReference, памʼять у довгоживучих процесах, новинки PHP 8.5. Мова на senior перетворюється на рантайм.
Архітектура рухається в протилежний бік: 1 картка junior, 5 middle, 6 senior. Це єдина тема, де senior-карток більше, ніж на будь-якому іншому рівні, і єдина, де junior-карток майже немає.
Форма питання змінюється раніше, ніж тема
Найчіткіше рівень видно не з теми, а з першого слова питання.
Формулювання «Що таке...» зустрічається в 7 junior-картках, 11 middle і 1 senior (це outbox). Питання про механіку й відмінності («як працює», «як влаштовано», «чим відрізняється») дають 17 карток junior, 10 middle і 2 senior. Обидві senior-картки з цієї групи знову про рантайм: як працює GC і як працюють OPcache з JIT.
А тепер зворотний зріз. Питання, сформульовані як вибір (починаються з «Коли» або містять «обирати/обираєте»), дають 2 картки junior, 1 middle і 9 senior. Причому всі шість питань банку, які починаються словом «Коли», це senior: коли DDD виправданий, коли потрібне партиціонування, коли доречний Multisite, коли виправданий headless WordPress, коли потрібні WeakMap і WeakReference, коли виправданий API Platform.
На junior вас просять описати механізм, і у відповіді є правильний варіант. На senior просять назвати умови, за яких ви беретесь або не беретесь, і оцінюють саме умови. Позначки «часто питають» досі не має жодна senior-картка: на 52 картках це вже було видно (з middle у senior), на 117 нічого не змінилось.
Що є тільки на senior
Одночасність лишається найщільнішим senior-кластером: тег «транзакції» стоїть на 4 senior-картках, 2 middle і жодній junior, а рівні ізоляції, deadlock-и, узгодженість при паралельних записах, outbox і ідемпотентні платежі існують у банку тільки як senior.
Другий блок, який живе виключно зверху, це рантайм і памʼять. Приклад з картки про WeakMap: кеш рішень на обʼєкт, який не має тримати цей обʼєкт живим.
final class AbilityCache
{
/** @var \WeakMap<object, array<string, bool>> */
private \WeakMap $decisions;
public function __construct()
{
$this->decisions = new \WeakMap();
}
public function remember(object $subject, string $ability, \Closure $resolve): bool
{
$known = $this->decisions[$subject] ?? [];
if (! array_key_exists($ability, $known)) {
$known[$ability] = $resolve();
$this->decisions[$subject] = $known;
}
return $known[$ability];
}
}
Під php-fpm різниці немає: процес помирає після відповіді. У воркері черги чи під Octane той самий кеш на SplObjectStorage тримає сильне посилання, і обʼєкти не звільняються до рестарту. Варіант з масивом по spl_object_id() гірший: ідентифікатор перевикористовується після знищення обʼєкта, тож кеш почне віддавати чуже рішення. WeakMap зʼявився у PHP 8.0 і відпускає ключ разом з обʼєктом.
Третій блок найменш очевидний для тих, хто готується сам, це сумісність. Чотири senior-картки говорять про одне й те саме з різних боків: версіонування API, Doctrine-міграції в команді, міграції БД при zero-downtime деплої, міграція з Yii2. Спільна умова скрізь однакова: дві версії живуть одночасно, і ваша зміна має пережити цей період.
public function toArray(Request $request): array
{
return [
'id' => $this->id,
// було 'new', стало 'created': зламано всіх, хто порівнює рядок
'status' => $this->status->value,
// було число, стало рядок після зміни типу колонки: зламано теж
'total' => (string) $this->total_cents,
// нове поле: старий клієнт його просто не читає, це безпечно
'paid_at' => $this->paid_at?->format(DATE_ATOM),
];
}
Додавання поля сумісне, зміна значення enum або типу поля вже ні, і клієнт дізнається про це в продакшені. Поруч стоїть експлуатаційний блок: горизонтальне масштабування, спостережуваність з трасуванням, партиціонування з архівацією. Тем, які просять вас керувати системою, а не писати код, на senior рівно стільки ж, скільки тем про код.
Що тягнеться через усі три рівні
Кілька ниток проходять банк наскрізь, змінюючи тільки глибину. Docker: локальне середовище на Compose (junior), збірка образу з multi-stage (middle), запуск на кількох серверах без локального стану (senior). Логи: Monolog і моніторинг (middle), OpenTelemetry і SLO (senior). Індекси: що таке індекс і коли він заважає (junior), порядок колонок у складеному індексі й читання EXPLAIN (middle), партиціонування й архівація (senior). DI: чотири junior-картки, дві middle, і senior-картка про Octane, яка посилається назад на junior-картку про контейнер.
Ці звʼязки я не домальовував, вони лежать у полі related. Шість senior-карток з 29 явно посилаються на junior-картки: версіонування API на composer.lock і semver, Octane і структура домену на service container, структура домену ще й на Form Request, Livewire проти SPA на життєвий цикл запиту, PHP 8.5 на readonly-властивості, WeakMap на замикання.
Ще показовіше працюють follow-up питання. У junior-картці про індекси четвертий follow-up звучить як «Чи можна додати індекс на робочій базі без простою?». Відповідь на нього це окрема senior-картка про zero-downtime міграції. Тема не зʼявляється раптово на senior-співбесіді, вона вже стоїть останнім рядком у junior-картці.
Метрики карток це підтверджують. Тегів на картку: 4.9 на junior, 5.1 на middle, 5.0 на senior. Follow-up питань: чотири-пʼять на кожному рівні. Приклад коду мовою PHP мають 29 карток з 36 junior, 43 з 52 middle і 23 з 29 senior, решта це SQL, bash, YAML і Dockerfile. Перелік не довшає, структура не ускладнюється.
Найкоротше це видно на одному фрагменті:
$orders = Order::query()
->with('customer')
->where('status', OrderStatus::Paid)
->orderByDesc('id')
->limit(50)
->get();
Junior-картка питає, чому with() перетворює 51 запит на 2. Middle-картка питає, який складений індекс закриває і фільтр, і сортування, і чому далі по сторінках OFFSET помре. Senior-картка питає, що робити, коли таблиця перестала вміщатись у кеш сторінок, а старі записи треба винести без простою. Код один і той самий.
Як готуватись за цими числами
Головне, що дає підрахунок: окремих «senior-тем», на які треба переходити, просто немає. З 29 senior-карток 6 прямо спираються на junior-матеріал, а решта продовжує ті самі вісім тем. Тому підготовка до senior це та сама база плюс три надбудови, яких унизу немає: одночасність, рантайм і памʼять, сумісність при живому трафіку.
Окремо потренуйте форму. Девʼять senior-карток сформульовані як вибір, і шість починаються з «Коли». Візьміть senior-підбірку і на кожну відповідайте двома реченнями умов, після яких ви беретесь, і одним реченням про те, коли не берете.
Третє стосується мови. Якщо ви йдете на senior і давно нічого не читали про PHP-рантайм, пʼять карток теми PHP закривають рівно цю прогалину: OPcache, JIT, GC, слабкі посилання і памʼять воркерів. Синтаксис на цьому рівні не питають взагалі.