Deep Engineering

Подготовка к собеседованию

Один механизм — один урок, в порядке частоты вопроса: сначала декораторы, итераторы и генераторы. Каждый урок разбирает механизм по документации и исходникам CPython — с замерами, а не по памяти.

Разделы
3
Статьи
46
ИСТОЧНИКОВ
184
Сложность
46 уроков · подходит

Разделы

Статьи

СреднийОпубликованоКанал в Go: значение мимо буфера, select без приоритетов и цена, которую платят парковкойСобеседование по каналам идёт лестницей: что лежит в переменной — куда попадает значение — что будет с закрытым и nil-каналом — как выбирает select — во что обходится буфер — где утекают горутины. Урок проходит её целиком: почему при ждущем получателе значение идёт мимо буфера, почему у ветки отмены в select нет приоритета и почему канал без буфера дороже втрое — но не потому, что «канал медленный».gochannelsselectgoroutinesconcurrency25 МИН3 ИСТОЧНИКОВЧитать →СреднийОпубликованоContext в Go: отмена только вниз, cancel, который не про отмену, и Value, который дорожает с глубинойСобеседование идёт лестницей: зачем контекст — почему первым аргументом — что делает cancel и почему его вызывают всегда — чем Canceled отличается от DeadlineExceeded — что класть в Value. Замерено: забытый cancel оставляет 123 байта на потомка навсегда, а Value на двадцати слоях дороже в 18 раз.gocontextcancellationtimeoutconcurrency30 МИН4 ИСТОЧНИКОВЧитать →СреднийОпубликованоdefer, panic и recover в Go: три момента вместо одного и recover, который молча не работаетСобеседование идёт лестницей: когда выполняется отложенный вызов — когда вычисляются его аргументы — в каком порядке — почему отложенная функция может изменить результат — что recover ловит и чего не ловит — и во что всё это обходится. Замерено: открытый defer 4,68 нс против 16,8 нс на виток в цикле, а паника против возврата ошибки — в 122 раза.godeferpanicrecoverruntime35 МИН4 ИСТОЧНИКОВЧитать →СреднийОпубликованоОшибки в Go: одна буква, которая рвёт цепочку, и утверждение типа, которое перестанет работатьСобеседование идёт лестницей: что такое error — чем %w отличается от %v — почему == не работает после обёртки — чем Is отличается от As — что делает Join — и сколько всё это стоит. Замерено: errors.Is на цепочке из двадцати обёрток дороже в 8,2 раза, а сама обёртка — это два выделения и 162 нс на пути ошибки.goerrorswrappingerrors-iserrors-as25 МИН4 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликованоСтек, куча и escape-анализ: почему указатель ещё ничего не значитГде живёт значение, в Go решает не программист и не наличие & — решает компилятор, и вопрос у него один: переживёт ли значение свой кадр. Замерено: указатель без ухода наружу даёт ноль выделений, а структура без единого указателя — одно; уход в кучу стоит ×12,8, и цена растёт вместе с размером значения.gomemoryescape-analysisperformancecompiler25 МИН3 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликованоСборщик мусора: почему пауза не растёт вместе с кучей, а GOGC ничего не ускоряетСборщик Go конкурентный и неперемещающий, и из этого выводится всё остальное: пауза короткая и от размера кучи не зависит, а GOGC — не ускоритель, а ручка обмена времени на память. Замерено: куча выросла в шестнадцать раз, медианная пауза — в 1,22, и даже эта разница меньше разброса между двумя повторами одного замера; GOGC=400 даёт вчетверо меньше циклов и в два с половиной раза больший пик памяти.gogcmemoryperformanceruntime25 МИН4 ИСТОЧНИКОВЧитать →СреднийОпубликованоИнтерфейс в Go: два слова, nil, который не nil, и вызов дороже в четыре разаСобеседование по интерфейсам идёт лестницей: что лежит в переменной — почему nil бывает не nil — когда упаковка выделяет память — во что обходится вызов через интерфейс — как устроено утверждение типа. Урок проходит её целиком, и всё выводится из одного: слов два, а не одно.gointerfacesniltype-assertionruntime30 МИН4 ИСТОЧНИКОВЧитать →СреднийОпубликованоКарта в Go: группа из восьми, порог 7/8 и промах, который дороже попаданияСобеседование по картам идёт лестницей: что лежит в переменной — как устроен поиск — как она расширяется — какая асимптотика — почему порядок обхода «случайный» — что будет при конкурентном доступе. Урок проходит её целиком, и на каждой ступени показывает механизм: почему восьмую часть слотов карта держит пустой намеренно, почему расширение происходит при 7/8, а не при заполнении, и почему промах ищется дольше попадания.gomapsswiss-tablehashruntime35 МИН3 ИСТОЧНИКОВЧитать →СреднийОпубликованоПолучатель метода в Go: копия, которой не видно, и «указатель быстрее», которое неверноСобеседование по получателю идёт лестницей: что такое получатель — почему изменения не видны — почему тип «не реализует» интерфейс — когда компилятор берёт адрес сам, а когда отказывается — и во что обходится копия. Урок проходит её целиком: замер показывает, что до восьми слов разницы нет вовсе, а на килобайте она десятикратна.gomethodsreceiversmethod-setsperformance25 МИН3 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликованоГорутины и планировщик Go: две тысячи байт, вытеснение и GOMAXPROCS, который ограничивает не то, что думаютСобеседование идёт лестницей: чем горутина отличается от потока — сколько она стоит — что такое GMP — что ограничивает GOMAXPROCS — вытесняющий ли планировщик — и когда горутина уходит с процессора. Замерено: 2000 байт стека плюс 500 байт структур, запуск с ожиданием дороже вызова в 82 раза, а уступка процессора стоит 106 нс.gogoroutinesschedulergmpgomaxprocs30 МИН4 ИСТОЧНИКОВЧитать →СреднийОпубликованоСрез в Go: окно над чужим массивом, два пути append и правило роста, которое все помнят неверноСрез не владеет данными: это окно над чужим массивом, и одна запись через одно окно видна во втором. Урок идёт от этой модели к механизму — два пути append, настоящая формула роста, что стоит за словами «амортизированно O(1)» и почему «×2 до 1024, дальше ×1,25» неверно в обоих числах.goslicesappendmemoryruntime35 МИН3 ИСТОЧНИКОВЧитать →СреднийОпубликованоСтроки, руны и байты: почему len не считает символы, а подстрока держит мегабайтыСтрока в Go — это заголовок из указателя и длины над неизменяемыми байтами. Отсюда всё остальное: len даёт байты, s[i] даёт байт, range даёт руны с байтовыми смещениями, подстрока бесплатна и потому удерживает весь исходный массив. Замерено: десять байт держат восемь мегабайт, а склейка тысячи кусков через += дороже Builder в 109 раз.gostringsunicodeutf-8memory30 МИН4 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликованоsync и atomic в Go: два совета, которые разваливаются при замереСобеседование идёт лестницей: что не так с обычным инкрементом — чем atomic отличается от мьютекса — когда брать RWMutex — что делает Once — почему копию мьютекса ловит go vet. Замерено: без конкуренции мьютекс дороже atomic вдвое, но с ростом числа претендентов он дорожает в 9,6 раза, а atomic — в 1,0; RWMutex же на короткой секции проигрывает обычному Mutex.gosyncatomicmutexconcurrency30 МИН4 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14*args и **kwargs: две звёздочки, которые стоят по-разному — и одна из них делает неизвестные имена допустимыми*args — кортеж, **kwargs — словарь, и на этом сходство кончается. На передаче звёздочка почти бесплатна, а две дороже вчетверо. А **kwargs, дописанный «для гибкости», превращает опечатку в имени аргумента из TypeError в молча принятое значение по умолчанию — и заодно выбрасывает подсказку, которой интерпретатор научился в 3.13.functionsargumentscpython-internals30 МИН6 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14async/await: вызов ничего не выполняет — и отсюда три ошибки, которые не падаютВызов async-функции не запускает её тело: он создаёт объект и возвращает управление. Из этого следует забытый await, который превращает любую проверку в истину; блокирующий вызов, который останавливает не одну задачу, а весь цикл событий вместе со всеми его задачами; и gather, у которого второе исключение остаётся при своей задаче, а вызывающему не достаётся.asyncioasync-awaitconcurrencycpython-internals40 МИН8 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.12 · 3.13 · 3.14Замыкания: захвачена переменная, а не значение — и цепочка областей проходит мимо тела классаЗамыкание держит ячейку, а не копию: значение читается в момент вызова, и одна ячейка достаётся всем внутренним функциям сразу. Привычная цепочка «локальная → объемлющая → глобальная → встроенные» умалчивает о двух вещах, на которых и ошибаются: тело класса в неё не входит, а присваивание где угодно в теле делает имя локальным для всего тела.closuresscopenonlocalcpython-internals35 МИН5 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14Контекстные менеджеры: два метода, одна возвращаемая истина и одна тихая потеря данныхПротокол — ровно два метода на типе. Но судьбу исключения после успешного входа определяет истинность одного значения: того, что вернул __exit__. Вернул истину — исключение исчезло вместе с остатком блока, и в логе не осталось ничего.context-managerswith-statementcontextlibcpython-internals40 МИН9 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14Включения: граница области видимости проходит внутри квадратных скобок, а не по нимПеременная включения наружу не течёт — это знают все. Но первый итерируемый вычисляется снаружи, а остальные части внутри, и в теле класса это даёт NameError на имя, написанное строкой выше. Наружу при этом всё-таки течёт одна вещь: морж.comprehensionsscopewalruscpython-internals35 МИН5 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликовано3.11 · 3.12 · 3.13 · 3.14Дескрипторы: одно правило приоритета, из которого растут property, методы и слотыДескриптор — это объект в классе, у которого есть __get__, __set__ или __delete__. Всё остальное — следствие одной строчки: дескриптор данных сильнее словаря экземпляра, дескриптор не-данных — слабее. Отсюда и работающий cached_property, и падающий @classmethod под чужим декоратором.descriptorsattributespropertycached-propertycpython-internals35 МИН6 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14Исключения и finally: одно слово, после которого функция перестаёт падатьСтоимость try, который не сработал, — около двух наносекунд, пять процентов, и байт-код объясняет почему. Дорого стоит другое: return в finally, который молча уничтожает исключение и превращает функцию в такую, из которой не выходит ни одна ошибка.exceptions-finallyexception-groupscpython-internals30 МИН6 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14Декораторы: выражения сверху вниз, применение снизу вверхДекоратор — обычный вызов, который случается один раз, когда выполняется def. Выражения при этом читаются сверху вниз, а применяются снизу вверх — из этой пары растёт всё остальное: и зачем нужен functools.wraps, и почему @classmethod под своим декоратором ломается молча.decoratorsfunctoolsdescriptorscpython-internals50 МИН9 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14is против ==: одно спрашивает про тождество, второе вызывает метод — и половина известных сюрпризов вообще не про них«is сравнивает объекты, == сравнивает значения» — верно и бесполезно: на вопрос «почему у меня 1000 is 1000 даёт True» это не отвечает. Ответов там три разных: кеш малых чисел, объединение констант компилятором и интернирование строк. Опираться в коде нельзя ни на один из трёх.identityequalityinterningnancpython-internals30 МИН7 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14Итераторы и генераторы: один метод разницы и одна тихая ошибкаИтерируемое умеет отдать итератор, итератор умеет отдать следующий элемент — вся разница в одном методе. Но именно из неё растёт ошибка, которая не падает: функция, дважды прошедшая по генератору, получает во второй раз пустоту и возвращает неверный ответ молча.iterators-vs-iterablesgeneratorsitertoolscpython-internals40 МИН8 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14lambda: то же самое, что def, кроме двух вещей — и знаменитая ошибка с циклом не из их числаУ lambda и def один тип, один байт-код тела и неотличимое время вызова. Разница в имени и в том, что тело — выражение. А знаменитая ошибка с циклом к lambda вообще не относится: обычный def ломается точно так же.lambdacpython-internals20 МИН6 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14Список против кортежа: 16 байт, которые получаются только при одном сравнении из трёх, и две операции, в которых разницы нет«Кортеж легче на 16 байт» верно, только если сравнивать его со списком, построенным литералом. Против выросшего через append та же разница — 72 байта, и getsizeof показывает её честно. А «кортеж быстрее» верно ровно для одной операции из пяти, и не потому, что он кортеж.listtupledata-structurescpython-internals40 МИН6 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликовано3.11 · 3.12 · 3.13 · 3.14Метаклассы: шесть шагов создания класса, из которых видно, что делать метаклассом, а что уже не надоМетакласс — это тип класса, и весь его протокол следует из одного этого. Порядок шагов рассыпан по трём разделам документации; собранный в колонку, он отвечает на все практические вопросы разом — включая тот, ради которого метаклассы обычно и не нужны.metaclasstypeinit-subclassclass-creationcpython-internals25 МИН6 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14Изменяемый аргумент по умолчанию: значение лежит в поле функции, и оно одно на все вызовыПравило «не пиши список в аргумент по умолчанию» запоминают все и всё равно нарушают, потому что оно выглядит произвольным запретом. Оно перестаёт быть произвольным, как только видно, где это значение лежит: в __defaults__ функции, созданное один раз при выполнении def.defaultsmutabilityfunctionsdataclasses25 МИН5 ИСТОЧНИКОВЧитать →СреднийОпубликовано3.11 · 3.12 · 3.13 · 3.14Копирование: присваивание не копирует ничего, copy — один уровень, deepcopy — граф целикомТри операции, которые путают между собой, потому что на плоском списке они дают одинаковый результат. Разница видна только на вложенности — и на ней же выясняется, что глубокая копия не «копирует всё подряд»: общий объект остаётся общим, цикл остаётся циклом, а __init__ не вызывается ни разу.copydeepcopymutabilityreferencescpython-internals30 МИН6 ИСТОЧНИКОВЧитать →СреднийОпубликованоОчередь accept: соединение установлено, а сервер о нём не знаетКлиент подключился, отправил запрос и ждёт. Сервер при этом не вызвал `accept` ни разу и о клиенте не подозревает. Между ними стоит очередь ядра — и почти всё, что выглядит как «сеть подтормаживает», на самом деле происходит в ней.srelinuxtcpbacklogaccept20 МИН3 ИСТОЧНИКОВЧитать →СреднийОпубликованоcgroups и OOM-killer: кого убьют, почему в логе пусто и что на самом деле значит 137Контейнер исчез, в логе приложения — ни строчки, в отчёте оркестратора — код 137. Это не загадка, а нормальная работа двух механизмов: учёт памяти по группе процессов и убийство сигналом, который нельзя перехватить. Урок разбирает оба и показывает прогоном, что потребление дошло до лимита и не превысило его: убивают не за превышение, а за то, что освобождать внутри группы стало нечего.srelinuxcgroupsmemoryoom25 МИН5 ИСТОЧНИКОВЧитать →СреднийОпубликованоКонсистентное хеширование: сколько ключей переезжает при смене состава«Хеш по модулю числа узлов» работает ровно до первого изменения состава. Посчитано точно: добавление одного узла к восьми переносит 88,9 % ключей — в восемь раз больше необходимого. Кольцо переносит 11,5 %, а при удалении узла не трогает ни одного чужого ключа. Цена — перекос, который без виртуальных узлов достигает 90 крат.sreshardinghashingdistributedload-balancing25 МИН1 ИСТОЧНИКЧитать →СреднийОпубликованоToo many open files: чей это лимит, почему он не общий и что считается дескрипторомОшибку «Too many open files» (пер.: «слишком много открытых файлов») чинят перезапуском и правкой конфига, редко разбираясь, чей именно лимит кончился. Урок показывает механизм целиком: два лимита вместо одного, наследование при запуске, и то, что дескриптор — это не файл, а любой открытый объект, включая каждое сетевое соединение.srelinuxrlimitfile-descriptorsemfile25 МИН4 ИСТОЧНИКОВЧитать →СреднийОпубликованоGraceful shutdown: откуда берутся пятисотки при каждой выкатке«При деплое немного ошибок» — не свойство природы, а следствие одного решения: что делает процесс, получив SIGTERM. Урок измеряет обе ветки на настоящем сервере под нагрузкой: немедленный выход обрывает все запросы в работе, дренаж не обрывает ни одного и стоит остаток начатой работы.sreshutdowndeploysignalsavailability20 МИН2 ИСТОЧНИКОВЧитать →СреднийОпубликованоHealth-checks: три разные проверки, которые зовут одним словомLiveness спрашивает «жив ли ты», readiness — «готов ли ты принимать», startup — «успел ли ты подняться». Их путают, и цена путаницы измерима: проба, завязанная на общую базу, превращает одну аварию базы в перезапуск всех реплик сразу.srehealth-checkslivenessreadinesscascade20 МИН1 ИСТОЧНИКЧитать →СреднийОпубликованоИдемпотентность: «ровно один раз» бывает в эффекте, а не в доставкеПовтор запроса — не решение, а способ сделать эффект дважды. Измерено: четыре повтора без ключа дали четыре эффекта, с ключом — один. Между эффектом и ответом есть окно, которое закрыть нельзя: измерено, как клиент видит отказ там, где на сервере уже всё случилось. А порядок работы с ключом решает всё: занять до работы — один эффект, записать после — два.sreidempotencyretriesdistributedreliability25 МИН1 ИСТОЧНИКЧитать →СреднийОпубликованоБалансировка: почему случайный выбор двух бьёт круговой переборКруговой перебор раздаёт запросы поровну — и именно поэтому проигрывает, как только запросы перестают быть одинаковыми. В модели он дал хвост в 582 мс там, где опрос двух случайных серверов дал 123,6, а опрос всех шестнадцати — 42,9. Урок про то, откуда берётся эта разница и почему на однородной нагрузке её не видно.sreload-balancingqueueinglatencynetworking25 МИН1 ИСТОЧНИКЧитать →СреднийОпубликованоРазрешение имени: приложение зовёт не DNS, а getaddrinfoМежду строкой «медленный DNS» в постмортеме и настоящим сервером имён стоит библиотека и три файла. Почти всё, за что ругают сеть, решается в них: ответ может прийти вообще без сети, кеша в процессе нет, а имя, которого не существует, стоит секунд, посчитанных из настроек, ещё до того, как начнёт тикать таймаут приложения.srelinuxdnsgetaddrinforesolver30 МИН3 ИСТОЧНИКОВЧитать →СреднийОпубликованоPage cache и fsync: почему «записали» и «не потеряется» — разные утверждения`write` возвращается до того, как данные окажутся на диске: они попадают в кеш ядра. Отсюда всё остальное — почему записанное переживает падение процесса, но при потере питания может быть потеряно, почему сброс дороже записи в десятки раз и почему база данных фиксирует транзакции пачками.srelinuxfsyncpage-cachedurability25 МИН3 ИСТОЧНИКОВЧитать →СреднийОпубликованоОчередь и хвост: почему рост нагрузки на 19 % даёт рост задержки вчетвероЗагрузка выросла с 0,80 до 0,95 — это плюс девятнадцать процентов. Время в системе выросло вчетверо. Урок разбирает, откуда берётся эта непропорциональность, и заодно опровергает ходячее «хвост растёт быстрее среднего»: в чистой очереди это не так, а причина расхождения хвоста и среднего — другая.srequeueinglatencypercentilescapacity25 МИН1 ИСТОЧНИКЧитать →СреднийОпубликованоРетраи и джиттер: почему сервис падает во второй раз в момент восстановленияПовтор запроса — самая дешёвая на вид мера и самый простой способ добить сервис: он добавляет нагрузку ровно тогда, когда её труднее всего принять, а одинаковое расписание повторов собирает тысячу клиентов в один миг. Урок разбирает механизм на модели: что с этим делает выдержка, что делает джиттер и чем за него приходится платить.sreretriesbackoffjitteroverload25 МИН1 ИСТОЧНИКЧитать →СреднийОпубликованоВыкатка и откат: почему канарейка не помогает без нужного детектораЧетыре стратегии выкатки на одном и том же дефекте — и неожиданный результат: одна и та же канарейка задевает в двадцать два раза меньше запросов, если тревога поднимается по доле ошибок. С детектором, который ждёт двухсот ошибок, она проигрывает переключению всего трафика разом.sredeploycanaryrollbackrelease20 МИН1 ИСТОЧНИКЧитать →СреднийОпубликованоСигналы, зомби и PID 1: почему контейнер не останавливается по SIGTERMРазговор идёт лестницей: что такое зомби — кому достаётся сирота — почему контейнер не останавливается сразу — что означает код 137. Первые две ступени держатся на том, что смерть процесса состоит из двух событий; последние две — на правиле ядра, которое почти никогда не называют вслух: у процесса с номером 1 в своём пространстве имён умолчательные действия сигналов не выполняются.srelinuxsignalsprocessescontainers25 МИН4 ИСТОЧНИКОВЧитать →СреднийОпубликованоSLO и бюджет ошибок: почему «99,9 %» без окна не значит ничегоОдна и та же цель означает 43 минуты в месяц и полторы минуты в сутки. Урок разбирает арифметику окна, показывает, что незаметный инцидент съедает бюджета больше заметного, и объясняет, почему выбор между календарным и скользящим окном — это выбор правила, по которому кончается заморозка выкаток.sresloerror-budgetreliabilitymetrics25 МИН1 ИСТОЧНИКЧитать →СреднийОпубликованоTIME_WAIT и эфемерные порты: клиент упирается раньше сервераДесятки тысяч сокетов в TIME_WAIT выглядят как утечка, а это нормальное состояние — и лечат его обычно не тем. Измерено: при обычном закрытии сокеты достаются стороне, которая закрывает активно; состояние живёт минуту; а упирается клиент не в память, а в номера портов, которых конечное число. Настройки, которые крутят вместо лечения, описаны в tcp(7) — и ни одна из них не про длительность.srelinuxtcpnetworkingtime-wait25 МИН3 ИСТОЧНИКОВЧитать →СреднийОпубликованоТаймаут и дедлайн: почему клиент ушёл, а работа продолжаетсяТаймаут ограничивает ожидание того, кто ждёт, и ничего не сообщает тому, кто работает. Отсюда две вещи, которые обычно обнаруживают в проде: цепочка из трёх сервисов с секундным таймаутом на каждом шаге может работать три секунды, а после ухода клиента вся цепочка продолжает занимать соединения и обработчики.sretimeoutsdeadlinesnetworkingcancellation25 МИН2 ИСТОЧНИКОВЧитать →СреднийОпубликованоTLS-рукопожатие: за что платит первое соединение«Возобновление сессии экономит круг» — фраза про TLS 1.2, которую продолжают говорить про 1.3. Измерено на звене с известной задержкой: полное рукопожатие 1.3 стоит один круг, 1.2 — два, а возобновление в 1.3 не экономит ни одного. Экономит оно другое, и это видно в байтах.sretlsnetworkinglatencyhandshake25 МИН2 ИСТОЧНИКОВЧитать →

Разобрано статьями

Эти механизмы спрашивают не реже, но разобраны они статьёй — целиком, с замерами и источниками.

Граф знаний

НЕЛИНЕЙНО · С УЧЁТОМ ПРЕДПОСЫЛОК

Темы связаны между собой, и связи здесь не нарисованы, а посчитаны: каждая статья сама называет, на что опирается и с чем соседствует, а картинка собирается из этих названий. Пунктирный кружок — тема, которую статьи уже называют связанной, но которая ещё не написана.

50 · 63 вершин и связей

Связи списком

Без связей: golang-maps, mutable-default-arg