Senior Go Interview Prep - Core Go: https://go.vbloher.org/docs/01-core-go/ - Механика defer в Go: https://go.vbloher.org/docs/01-core-go/defer/ - Встраивание структур и интерфейсов (Embedding): https://go.vbloher.org/docs/01-core-go/embedding/ - Ошибки в Go: error, wrapping, errors.Is/As/Join: https://go.vbloher.org/docs/01-core-go/errors/ - Дженерики в Go (1.18+): https://go.vbloher.org/docs/01-core-go/generics/ - Интерфейсы в Go: https://go.vbloher.org/docs/01-core-go/interfaces/ - Устройство map в Go: https://go.vbloher.org/docs/01-core-go/maps/ - panic / recover: механика, раскрутка стека и runtime-паники: https://go.vbloher.org/docs/01-core-go/panic-recover/ - Указатели в Go: https://go.vbloher.org/docs/01-core-go/pointers/ - Рефлексия в Go (reflect): https://go.vbloher.org/docs/01-core-go/reflection/ - Внутреннее устройство слайсов в Go: https://go.vbloher.org/docs/01-core-go/slices/ - Строки, руны и байты в Go: https://go.vbloher.org/docs/01-core-go/strings-runes-bytes/ - Система типов Go: defined types, alignment, memory layout: https://go.vbloher.org/docs/01-core-go/type-system/ - Concurrency: https://go.vbloher.org/docs/02-concurrency/ - sync/atomic: https://go.vbloher.org/docs/02-concurrency/atomic/ - Буферизованные vs небуферизованные каналы: https://go.vbloher.org/docs/02-concurrency/buffered-unbuffered/ - Канал vs Mutex: когда что выбрать: https://go.vbloher.org/docs/02-concurrency/channel-vs-mutex/ - Каналы: устройство hchan: https://go.vbloher.org/docs/02-concurrency/channels/ - Утечки горутин, дедлоки, livelock, starvation: https://go.vbloher.org/docs/02-concurrency/common-leaks-deadlocks/ - sync.Cond: https://go.vbloher.org/docs/02-concurrency/cond/ - context: https://go.vbloher.org/docs/02-concurrency/context/ - Горутины: жизненный цикл, стоимость, стек: https://go.vbloher.org/docs/02-concurrency/goroutines-lifecycle/ - sync.Mutex и sync.RWMutex: https://go.vbloher.org/docs/02-concurrency/mutex-rwmutex/ - sync.Once: https://go.vbloher.org/docs/02-concurrency/once/ - Паттерны конкурентности: https://go.vbloher.org/docs/02-concurrency/patterns/ - Race Detector (гонки данных и -race): https://go.vbloher.org/docs/02-concurrency/race-detector/ - Планировщик GMP: https://go.vbloher.org/docs/02-concurrency/scheduler-gmp/ - select: https://go.vbloher.org/docs/02-concurrency/select/ - sync.WaitGroup: https://go.vbloher.org/docs/02-concurrency/waitgroup/ - Runtime и память: https://go.vbloher.org/docs/03-runtime-memory/ - Паттерны аллокаций и снижение давления на GC: https://go.vbloher.org/docs/03-runtime-memory/allocation-patterns/ - Escape Analysis: когда переменная убегает в кучу: https://go.vbloher.org/docs/03-runtime-memory/escape-analysis/ - Сборщик мусора Go: concurrent tri-color mark-sweep: https://go.vbloher.org/docs/03-runtime-memory/gc/ - Тюнинг GC: GOGC и GOMEMLIMIT: https://go.vbloher.org/docs/03-runtime-memory/gogc-gomemlimit/ - GOMAXPROCS: параллелизм планировщика и проблема контейнеров: https://go.vbloher.org/docs/03-runtime-memory/gomaxprocs/ - Утечки горутин (goroutine leaks): https://go.vbloher.org/docs/03-runtime-memory/goroutine-leaks/ - Утечки памяти в Go (несмотря на GC): https://go.vbloher.org/docs/03-runtime-memory/memory-leaks/ - Модель памяти Go (Go Memory Model): happens-before и синхронизация: https://go.vbloher.org/docs/03-runtime-memory/memory-model/ - pprof: профилирование CPU, памяти и блокировок в Go: https://go.vbloher.org/docs/03-runtime-memory/pprof/ - Execution Tracer и runtime/trace: тайминги вместо агрегатов: https://go.vbloher.org/docs/03-runtime-memory/runtime-tracing/ - Стек vs Куча: где живут данные в Go: https://go.vbloher.org/docs/03-runtime-memory/stack-vs-heap/ - Тестирование: https://go.vbloher.org/docs/04-testing/ - testify, assert/require и golden files: https://go.vbloher.org/docs/04-testing/assertions-testify/ - Бенчмарки в Go: https://go.vbloher.org/docs/04-testing/benchmarks/ - Покрытие, -race и флаки-тесты: https://go.vbloher.org/docs/04-testing/coverage-race/ - Нативный fuzzing в Go (1.18+): https://go.vbloher.org/docs/04-testing/fuzzing/ - Интеграционные тесты, testcontainers-go, TestMain: https://go.vbloher.org/docs/04-testing/integration-testcontainers/ - Моки, стабы и тестируемость: https://go.vbloher.org/docs/04-testing/mocks/ - Table-driven тесты, subtests и параллельность: https://go.vbloher.org/docs/04-testing/table-driven/ - Backend: https://go.vbloher.org/docs/05-backend/ - Аутентификация и авторизация: AuthN/AuthZ, сессии vs токены, RBAC/ABAC, API keys, mTLS, секреты: https://go.vbloher.org/docs/05-backend/auth-authz/ - Graceful Shutdown HTTP/gRPC сервера в Go: https://go.vbloher.org/docs/05-backend/graceful-shutdown/ - gRPC: типы RPC, интерсепторы, контекст, метаданные, error model: https://go.vbloher.org/docs/05-backend/grpc/ - JWT (JSON Web Token): https://go.vbloher.org/docs/05-backend/jwt/ - Middleware-паттерн в Go: https://go.vbloher.org/docs/05-backend/middleware/ - net/http: Server, Handler, ServeMux, таймауты, Client и контекст: https://go.vbloher.org/docs/05-backend/net-http/ - OAuth2: роли, grant types, OIDC, токены и типовые ошибки: https://go.vbloher.org/docs/05-backend/oauth2/ - OpenAPI/Swagger, code generation, contract-first vs code-first, валидация: https://go.vbloher.org/docs/05-backend/openapi/ - Protocol Buffers: схемы, wire format, эволюция и совместимость: https://go.vbloher.org/docs/05-backend/protobuf/ - REST: принципы, версионирование, идемпотентность, статусы, пагинация, ошибки: https://go.vbloher.org/docs/05-backend/rest/ - Сети и протоколы: https://go.vbloher.org/docs/06-networking/ - Пулы соединений: http.Transport, БД, утечки: https://go.vbloher.org/docs/06-networking/connection-pooling/ - DNS: записи, резолвинг, кэширование, DNS в Go: https://go.vbloher.org/docs/06-networking/dns/ - Версии HTTP: 1.1, 2, 3: https://go.vbloher.org/docs/06-networking/http-versions/ - TCP/IP: модель, транспорт и что важно бэкендеру: https://go.vbloher.org/docs/06-networking/tcp-ip/ - TLS: handshake, сертификаты, mTLS, производительность: https://go.vbloher.org/docs/06-networking/tls/ - UDP и надёжность поверх UDP: https://go.vbloher.org/docs/06-networking/udp/ - WebSocket: upgrade, фреймы, масштабирование: https://go.vbloher.org/docs/06-networking/websocket/ - Базы данных: https://go.vbloher.org/docs/07-databases/ - Пул соединений к PostgreSQL в Go: database/sql, pgx, pgxpool, PgBouncer: https://go.vbloher.org/docs/07-databases/connection-pooling-pgx/ - Взаимоблокировки (Deadlocks) в PostgreSQL: https://go.vbloher.org/docs/07-databases/deadlocks/ - Индексы в PostgreSQL: https://go.vbloher.org/docs/07-databases/indexes/ - Уровни изоляции транзакций в PostgreSQL: https://go.vbloher.org/docs/07-databases/isolation-levels/ - MVCC в PostgreSQL: версии строк, видимость, VACUUM и bloat: https://go.vbloher.org/docs/07-databases/mvcc/ - Обзор NoSQL и Redis: https://go.vbloher.org/docs/07-databases/nosql-redis/ - Партиционирование таблиц в PostgreSQL: https://go.vbloher.org/docs/07-databases/partitioning/ - Архитектура PostgreSQL: https://go.vbloher.org/docs/07-databases/postgresql-architecture/ - Планирование и оптимизация запросов в PostgreSQL: https://go.vbloher.org/docs/07-databases/query-planning/ - Репликация в PostgreSQL: https://go.vbloher.org/docs/07-databases/replication/ - Шардирование (горизонтальное масштабирование): https://go.vbloher.org/docs/07-databases/sharding/ - Транзакции в PostgreSQL и Go (database/sql, pgx): https://go.vbloher.org/docs/07-databases/transactions/ - Распределённые системы: https://go.vbloher.org/docs/08-distributed-systems/ - CAP теорема: https://go.vbloher.org/docs/08-distributed-systems/cap-theorem/ - Circuit Breaker: https://go.vbloher.org/docs/08-distributed-systems/circuit-breaker/ - Консенсус и Raft: репликация состояния в присутствии отказов: https://go.vbloher.org/docs/08-distributed-systems/consensus-raft/ - Модели согласованности: https://go.vbloher.org/docs/08-distributed-systems/consistency/ - Гарантии доставки сообщений: at-most-once / at-least-once / exactly-once: https://go.vbloher.org/docs/08-distributed-systems/delivery-guarantees/ - Eventual Consistency: https://go.vbloher.org/docs/08-distributed-systems/eventual-consistency/ - Идемпотентность в распределённых системах: https://go.vbloher.org/docs/08-distributed-systems/idempotency/ - Apache Kafka: https://go.vbloher.org/docs/08-distributed-systems/kafka/ - Transactional Outbox: https://go.vbloher.org/docs/08-distributed-systems/outbox/ - RabbitMQ: AMQP 0-9-1, маршрутизация, надёжность доставки и сравнение с Kafka: https://go.vbloher.org/docs/08-distributed-systems/rabbitmq/ - Ретраи: backoff, jitter, budgets и идемпотентность: https://go.vbloher.org/docs/08-distributed-systems/retries/ - Saga Pattern: https://go.vbloher.org/docs/08-distributed-systems/saga/ - Observability: https://go.vbloher.org/docs/09-observability/ - Grafana: https://go.vbloher.org/docs/09-observability/grafana/ - Метрики: RED, USE, Golden Signals: https://go.vbloher.org/docs/09-observability/metrics/ - OpenTelemetry: https://go.vbloher.org/docs/09-observability/opentelemetry/ - Prometheus: https://go.vbloher.org/docs/09-observability/prometheus/ - SLI / SLO / SLA: https://go.vbloher.org/docs/09-observability/slo-sli/ - Структурированное логирование (slog): https://go.vbloher.org/docs/09-observability/structured-logging/ - Distributed Tracing: https://go.vbloher.org/docs/09-observability/tracing/ - System Design: https://go.vbloher.org/docs/10-system-design/ - Analytics Pipeline: https://go.vbloher.org/docs/10-system-design/analytics-pipeline/ - Chat System: https://go.vbloher.org/docs/10-system-design/chat/ - Фреймворк System Design интервью: https://go.vbloher.org/docs/10-system-design/framework/ - Notification Service: https://go.vbloher.org/docs/10-system-design/notification-service/ - Order Service: https://go.vbloher.org/docs/10-system-design/order-service/ - Payment Service: https://go.vbloher.org/docs/10-system-design/payment-service/ - Rate Limiter: https://go.vbloher.org/docs/10-system-design/rate-limiter/ - URL Shortener: https://go.vbloher.org/docs/10-system-design/url-shortener/ - DevOps: https://go.vbloher.org/docs/11-devops/ - CI/CD: пайплайны, стадии, стратегии деплоя: https://go.vbloher.org/docs/11-devops/cicd/ - Облака (AWS / GCP) для бэкендера: https://go.vbloher.org/docs/11-devops/cloud-aws-gcp/ - Docker для Go-разработчика: https://go.vbloher.org/docs/11-devops/docker/ - GitHub Actions и GitLab CI: https://go.vbloher.org/docs/11-devops/github-gitlab-ci/ - Kubernetes для Go-разработчика: https://go.vbloher.org/docs/11-devops/kubernetes/ - Terraform / Infrastructure as Code: https://go.vbloher.org/docs/11-devops/terraform/ - Алгоритмы: https://go.vbloher.org/docs/12-algorithms/ - Типовые алгоритмические задачи и паттерны: https://go.vbloher.org/docs/12-algorithms/common-problems/ - Асимптотическая сложность (Big-O): https://go.vbloher.org/docs/12-algorithms/complexity/ - Структуры данных в Go: https://go.vbloher.org/docs/12-algorithms/data-structures/ - Специфика live-coding на Go: https://go.vbloher.org/docs/12-algorithms/go-specifics/ - Behavioral: https://go.vbloher.org/docs/13-behavioral/ - Конфликты, разногласия и работа со стейкхолдерами: https://go.vbloher.org/docs/13-behavioral/conflicts/ - Как проходит senior-интервью: этапы, оценка, оффер: https://go.vbloher.org/docs/13-behavioral/interview-flow/ - Лидерство и менторство: https://go.vbloher.org/docs/13-behavioral/leadership-mentoring/ - Типовые поведенческие вопросы для Senior: https://go.vbloher.org/docs/13-behavioral/senior-questions/ > Модуль: Core Go · Уровень: Senior ## TL;DR `string` в Go — это неизменяемая (immutable) пара «указатель + длина» (`StringHeader{ptr, len}`), указывающая на read-only последовательность байт, без какого-либо требования к кодировке. Текст в исходниках и стандартной библиотеке хранится в UTF-8, поэтому `len(s)` возвращает количество **байт**, `s[i]` — отдельный **байт**, а `for i, r := range s` декодирует UTF-8 и отдаёт **руны** (`rune = int32`, Unicode code point) вместе с байтовым смещением. Конвертации `string<->[]byte` и `string<->[]rune` в общем случае копируют память (аллокация в куче), но компилятор умеет несколько важных оптимизаций (ключ map, `range []byte(s)`), а `strings.Builder` и `unsafe.String/Slice` позволяют избегать лишних копий — каждая со своими опасностями. ## Теория ### string под капотом: StringHeader Строка на уровне рантайма представлена структурой из двух машинных слов: ```go // reflect.StringHeader (концептуально) type stringStruct struct { str unsafe.Pointer // указатель на массив байт len int // длина в БАЙТАХ } ``` На 64-битной платформе `unsafe.Sizeof("")` == 16 (8 байт указатель + 8 байт длина). Важные следствия: - **Строка — это value type из двух слов.** Передача строки в функцию копирует header (16 байт), а не сам массив байт. Сам массив разделяется (shared). - **Immutability гарантируется компилятором и рантаймом.** Память, на которую указывает строка, помечена как read-only (строковые литералы кладутся в read-only сегмент бинарника). Попытка записи через `unsafe` — UB, часто segfault. - **Нет требования к кодировке.** Строка — это просто байты. UTF-8 — это лишь соглашение, которому следуют литералы, `range`, `fmt` и пакет `unicode/utf8`. В строке могут лежать произвольные байты, в т.ч. невалидный UTF-8. ```go s := "héllo" fmt.Println(len(s)) // 6 (é занимает 2 байта в UTF-8) fmt.Println(utf8.RuneCountInString(s)) // 5 ``` ### Отличие от []byte | | `string` | `[]byte` | |---|---|---| | Структура | `{ptr, len}` (2 слова) | `{ptr, len, cap}` (3 слова, slice header) | | Изменяемость | immutable | mutable | | Память | как правило read-only | read-write куча/стек | | Сравнение `==` | да (поэлементно) | нет (только `bytes.Equal`) | | Ключ map | да | нет | | Zero value | `""` | `nil` | `[]byte` — это slice header из трёх слов, потому что у него есть `cap` для роста через `append`. У строки роста нет, поэтому `cap` не нужен. ### rune и UTF-8 `rune` — это псевдоним `int32`, представляющий **Unicode code point** (значение от 0 до 0x10FFFF). Это НЕ «символ» в бытовом смысле: один видимый глиф (grapheme cluster) может состоять из нескольких code points (например, emoji с модификаторами или базовая буква + комбинируемый диакритик). UTF-8 кодирует code point в 1–4 байта: | Code point | Байт | Шаблон | |---|---|---| | U+0000–U+007F (ASCII) | 1 | `0xxxxxxx` | | U+0080–U+07FF | 2 | `110xxxxx 10xxxxxx` | | U+0800–U+FFFF | 3 | `1110xxxx 10xxxxxx 10xxxxxx` | | U+10000–U+10FFFF | 4 | `11110xxx 10xxxxxx 10xxxxxx 10xxxxxx` | Свойства UTF-8, которые важно знать: - ASCII (0–127) кодируется одним байтом, идентичным самому коду — обратная совместимость. - Ведущий байт многобайтовой последовательности всегда отличается от continuation-байтов (`10xxxxxx`), поэтому можно безопасно «прыгать» по строке вперёд/назад и находить границы рун. - UTF-8 self-synchronizing: потеряв позицию, можно восстановить её, пропуская continuation-байты. - Невалидный байт декодируется в `utf8.RuneError` (U+FFFD, «replacement character»), `RuneError` == `'�'`. ### Итерация: range даёт руны, индексация — байты Ключевое senior-различие: ```go s := "aé文" // range декодирует UTF-8: i — байтовое смещение начала руны, r — руна for i, r := range s { fmt.Printf("byte=%d rune=%c (U+%04X)\n", i, r, r) } // byte=0 rune=a (U+0061) // byte=1 rune=é (U+00E9) <- следующий i прыгнул на 3, т.к. é = 2 байта // byte=3 rune=文 (U+6587) // индексация даёт ОДИН байт, тип byte (uint8) fmt.Println(s[1]) // 195 — первый байт двухбайтовой é, НЕ 'é' ``` Под капотом `range` по строке вызывает декодер UTF-8 (аналог `utf8.DecodeRuneInString`) на каждой итерации. При встрече невалидного байта возвращает `RuneError` и сдвигается на 1 байт. Это означает: длина цикла `range` по строке = число рун, а не байт; обычный `for i := 0; i < len(s); i++` обходит байты. ```go len(s) // байты utf8.RuneCountInString(s) // руны (быстрее, чем []rune(s), т.к. без аллокации) ``` ### Конвертации и их стоимость ```go b := []byte(s) // копирует len(s) байт в новый слайс (аллокация в куче) s2 := string(b) // копирует len(b) байт в новую строку (аллокация) r := []rune(s) // декодирует ВСЮ строку, аллоцирует []int32 (4 байта на руну) s3 := string(r) // кодирует каждую руну в UTF-8 (аллокация) ``` Почему копирование обязательно в общем случае: - `string` immutable, `[]byte` mutable. Если бы `string(b)` делил память с `b`, изменение `b` нарушило бы immutability строки. Поэтому — копия. - `[]rune(s)` принципиально другой layout (4 байта на code point вместо 1–4 байт UTF-8), декодирование неизбежно. **Стоимость:** O(n) по времени и памяти. Для горячего пути это значимо. `[]rune` особенно дорог: для ASCII он раздувает память в 4 раза. ### Оптимизации компилятора (без аллокации) Компилятор Go распознаёт несколько идиом и убирает копию/аллокацию: 1. **`string([]byte)` как ключ map** — поиск/чтение без аллокации: ```go m[string(b)]++ // не аллоцирует временную строку v, ok := m[string(b)] // тоже ``` Компилятор знает, что временная строка живёт только во время lookup и не «утекает», поэтому делает поиск прямо по байтам. 2. **`range []byte(s)`** — итерация по байтам строки без аллокации слайса: ```go for i, c := range []byte(s) { ... } // []byte(s) не аллоцируется ``` 3. **Сравнение `string(b) == "literal"`** — без аллокации строки из `b`. 4. **`switch string(b)` / `if string(b) == ...`** в ряде случаев. Эти оптимизации завязаны на escape-анализ: временная строка не должна «убегать». Проверить можно через `go build -gcflags='-m'`. ### strings.Builder `strings.Builder` амортизирует аллокации при склейке строк, накапливая байты во внутреннем `[]byte` и отдавая итог через `unsafe`-конверсию без финальной копии. ```go type Builder struct { addr *Builder // of receiver, для copyCheck buf []byte } func (b *Builder) String() string { return *(*string)(unsafe.Pointer(&b.buf)) // НЕ копирует buf } ``` Ключевые моменты: - **Без копии в `String()`.** `buf` интерпретируется как строка через `unsafe`. Это безопасно, потому что Builder инвариантом не отдаёт наружу мутабельный доступ к уже «застроканному» буферу (после `String()` буфер логически не должен меняться так, чтобы порушить выданную строку; на практике дальнейший `WriteString` может вызвать `append` с новым массивом). - **copyCheck: Builder нельзя копировать.** Поле `addr` хранит адрес самого Builder при первом использовании. Если структуру скопировали (например, передали по значению), `b.addr != b` — `copyCheck` паникует: ```go func (b *Builder) copyCheck() { if b.addr == nil { b.addr = (*Builder)(noescape(unsafe.Pointer(b))) } else if b.addr != b { panic("strings: illegal use of non-zero Builder copied by value") } } ``` Причина: после копирования две копии делили бы один `buf`; `unsafe`-выданные строки могли бы стать невалидными при росте одной из копий. `go vet` (copylocks-подобная проверка) и рантайм-паника защищают от этого. - **`Grow(n)`** заранее резервирует ёмкость, минимизируя реаллокации. - Реализует `io.Writer`, `WriteByte`, `WriteRune`, `WriteString`. ### unsafe-конвертации без копирования (Go 1.20+) До Go 1.20 использовали хаки через `reflect.StringHeader`/`SliceHeader`, которые формально некорректны (GC мог не «увидеть» указатель). Начиная с Go 1.20 есть официальные функции: ```go // []byte -> string без копии: строка ссылается на тот же массив s := unsafe.String(unsafe.SliceData(b), len(b)) // string -> []byte без копии b := unsafe.Slice(unsafe.StringData(s), len(s)) ``` Опасности (почему это `unsafe`): - **Нарушение immutability.** `unsafe.Slice(StringData(s), len(s))` даёт мутабельный доступ к памяти строки. Запись в него — UB; для строковых литералов это запись в read-only память → segfault. - **Lifetime / GC.** Нужно гарантировать, что исходный объект жив, пока используется результат. `unsafe.StringData`/`SliceData` дают сырой указатель. - **`cap` у результата `unsafe.Slice` равен переданной длине**, но запись всё равно запрещена для строк. - Применять только когда вы владеете байтами и гарантируете, что после конвертации `[]byte` больше не мутируется (классический паттерн: построили буфер, отдали как строку, дальше его не трогаем). ### Сравнение строк - `==`, `!=` — поэлементное (побайтовое) сравнение. Сначала рантайм сравнивает длины и указатели (если ptr+len совпадают — равны мгновенно), иначе `memequal`. - `<`, `>`, `<=`, `>=` — лексикографическое сравнение **по байтам** (фактически по UTF-8), не по code points в смысле локали и не с учётом регистра. Для валидного UTF-8 побайтовое сравнение совпадает с порядком по code points (свойство кодировки). - `strings.Compare` существует, но `==`/`<` обычно эффективнее и идиоматичнее; `Compare` нужен для трёхзначного результата. - Сравнение НЕ нормализует Unicode: «é» как один code point (U+00E9) и «e»+комбинируемый акут (U+0065 U+0301) — разные строки. Для нормализации нужен `golang.org/x/text/unicode/norm`. ## Подводные камни / gotchas - **`s[i]` — это `byte`, а не символ.** Для не-ASCII вернёт середину UTF-8 последовательности. ```go s := "é" fmt.Println(s[0]) // 195 fmt.Printf("%c\n", s[0]) // Ã — мусор ``` - **`len(s)` ≠ число символов.** Для подсчёта рун — `utf8.RuneCountInString(s)`. - **«Реверс строки» через байты ломает UTF-8.** Реверсить нужно руны: `[]rune(s)`, развернуть, обратно `string`. И даже это сломает grapheme clusters (emoji с модификаторами, комбинируемые диакритики). - **`string(int)` — это НЕ форматирование числа, а code point.** `string(65)` == `"A"`. С Go 1.15+ `go vet` ругается на `string(int)`; для чисел используйте `strconv.Itoa`/`fmt.Sprint`. `string(rune(65))` — корректный явный способ. - **Конкатенация в цикле `s += ...` — O(n²)** по памяти/времени (каждый `+=` аллоцирует новую строку). Используйте `strings.Builder`. - **Подстрока через slicing делит память с оригиналом.** `big[0:10]` ссылается на тот же массив, что и `big` (огромная строка) — большой буфер не освобождается GC, пока жива подстрока. Лечится копией: `string([]byte(big[0:10]))`. - **`[]rune(s)` для ASCII тратит ×4 памяти** — не используйте, если достаточно байт/рун-итерации. - **Копирование `strings.Builder` по значению → паника** при следующей записи (`copyCheck`). Передавайте только по указателю. - **Невалидный UTF-8 в `range` молча превращается в U+FFFD** и сдвиг на 1 байт — можно не заметить порчу данных. - **`unsafe.String`/`Slice` + последующая мутация байт = UB**, часто незаметный (данные «портятся» уже выданной строки) или segfault на литералах. ## Вопросы на собеседовании **В:** Что такое `string` в Go на уровне рантайма и сколько памяти занимает переменная типа string? **О:** Это структура из двух машинных слов: указатель на массив байт и длина в байтах (`{ptr, len}`). На 64-бит — 16 байт (`unsafe.Sizeof("") == 16`). Сам массив байт хранится отдельно (для литералов — в read-only сегменте) и может разделяться между строками. Передача строки копирует только header, не данные. **В:** Почему `len("привет")` возвращает 12, а не 6? **О:** `len` для строки возвращает число **байт**, а не рун. Текст хранится в UTF-8; кириллица лежит в диапазоне U+0400–U+04FF, который кодируется двумя байтами на символ, поэтому 6 букв × 2 = 12 байт. Для подсчёта рун — `utf8.RuneCountInString`. **В:** Чем отличается `for i, c := range s` от `for i := 0; i < len(s); i++`? **О:** `range` по строке декодирует UTF-8: `c` имеет тип `rune` (code point), `i` — байтовое смещение начала руны (шаг по строке неравномерный — 1–4 байта). Обычный цикл по индексу обходит **байты**: `s[i]` имеет тип `byte`. На невалидном UTF-8 `range` отдаёт `RuneError` (U+FFFD) и сдвигается на 1 байт. **В:** Почему конвертация `[]byte(s)` копирует данные? Можно ли без копии? **О:** Потому что `string` immutable, а `[]byte` mutable; разделяя память, можно было бы изменить строку через слайс, что нарушило бы инварианты языка и GC/оптимизаций. Поэтому в общем случае копия обязательна (O(n), аллокация). Без копии — только через `unsafe.Slice(unsafe.StringData(s), len(s))` (Go 1.20+), но тогда вы обязаны не мутировать результат (для литералов это segfault) и следить за lifetime. **В:** Какие конвертации `string`↔`[]byte` компилятор оптимизирует до zero-alloc? **О:** Несколько идиом, опирающихся на escape-анализ: `m[string(b)]` (доступ к map по ключу), `string(b) == "literal"` (сравнение), `range []byte(s)` (итерация), `switch string(b)`. Во всех случаях временная строка/слайс не «убегает» из выражения, поэтому копия не делается. Проверяется через `-gcflags=-m`. **В:** Как `strings.Builder` избегает финальной аллокации в `String()` и почему его нельзя копировать? **О:** Внутри он накапливает байты в `[]byte buf`, а в `String()` интерпретирует этот буфер как строку через `unsafe.Pointer` без копирования. Нельзя копировать, потому что после копии две структуры разделяли бы один `buf`; рост одной (через `append` с реаллокацией) или мутация порушили бы уже выданные `unsafe`-строки и инварианты. Защита — `copyCheck`: при первом использовании Builder запоминает свой адрес в поле `addr`; если `addr != &b`, значит копировали по значению → паника. **В:** `string(65)` — что вернёт и почему это потенциальная ошибка? **О:** Вернёт `"A"`, потому что конвертация целого в строку трактует число как Unicode code point, а не форматирует его в десятичную запись. Частая ошибка новичков, ожидающих `"65"`. `go vet` предупреждает об этом начиная с Go 1.15. Для числа в текст — `strconv.Itoa`; для явного code point — `string(rune(65))`. **В:** Как лексикографически сравниваются строки и совпадает ли это с порядком Unicode code points? **О:** `<`/`>` сравнивают строки **побайтово** (по UTF-8 представлению). Для валидного UTF-8 это совпадает с порядком по code points — свойство кодировки (UTF-8 сохраняет порядок code points в байтовом сравнении). `==` сначала сравнивает длины (и при совпадении ptr — мгновенно равны), иначе `memequal`. Сравнение не нормализует Unicode и не учитывает регистр/локаль. **В:** Почему `s += part` в цикле — плохо, и что произойдёт с памятью? **О:** Строки immutable, поэтому каждый `+=` создаёт новую строку, копируя весь уже накопленный результат — это O(n²) копирований и множество мусорных аллокаций. Решение — `strings.Builder` (или `bytes.Buffer`), который накапливает в растущем буфере с амортизированной O(1) вставкой, плюс `Grow` для предаллокации. ## На что копают на senior+ - **Escape-анализ и zero-alloc идиомы.** Senior должен не просто знать про оптимизацию `m[string(b)]`, а уметь объяснить, что она держится на escape-анализе, и показать через `-gcflags=-m`, когда она ломается (например, если временную строку сохранить в переменную — копия появится). - **Memory pinning через подстроки.** Follow-up: «У вас в кэше лежат короткие подстроки от гигабайтного ответа, память не освобождается — почему?» Ответ: slicing строки делит backing array; короткая подстрока удерживает весь буфер. Лечение — `strings.Clone` (Go 1.18+) или копия через `[]byte`. - **Grapheme clusters vs runes vs code points.** «Реверс emoji» или «обрезать строку до N символов» — ловушка: руна ≠ видимый символ. Senior упомянет нормализацию (NFC/NFD) и grapheme clusters (`x/text`), что обычный кандидат игнорирует. - **Корректность `unsafe.String`/`Slice`.** Спросят про lifetime, read-only память литералов, почему старый хак через `reflect.SliceHeader` некорректен с точки зрения GC (нет указателя, который GC отслеживает; объект может быть собран). Знание перехода на `unsafe.StringData`/`SliceData` в 1.20 — маркер актуальности. - **Внутренности `strings.Builder`.** Follow-up про `copyCheck`, `noescape`, почему `String()` безопасен несмотря на `unsafe`, и чем Builder отличается от `bytes.Buffer` (Builder — write-only, оптимизирован под итоговую строку; Buffer — read-write, реализует `io.Reader`). - **UTF-8 self-synchronization.** Могут спросить, почему можно искать границы рун в обе стороны без полного парсинга с начала — ответ про различимость ведущих и continuation-байтов. - **Сравнение производительности `RuneCountInString` vs `len([]rune(s))`** — первое не аллоцирует, второе аллоцирует весь слайс рун; senior выберет первое и объяснит почему.