s := make([]int, 3, 5) // len 3, cap 5: [0 0 0 _ _]
s = append(s, 1) // len 4, тот же массив
s = append(s, 2, 3) // len 6 > cap 5 — новый массив, копирование
Почему присваивать результат append. Функция возвращает новый дескриптор: длина изменилась всегда, а указатель — если массив перевыделен. Старая переменная остаётся с прежней длиной.
Рост ёмкости. Пока срез маленький (до 256 элементов в Go 1.18+), ёмкость удваивается, дальше растёт плавнее, с округлением до размеров классов аллокатора. Амортизированная стоимость append — O(1), как у ArrayList.
Срез среза s[1:3] — новый дескриптор над тем же массивом: изменения элементов видны в обоих. Полное выражение среза s[1:3:3] ограничивает ёмкость, и следующий append гарантированно создаст новый массив.
Утечка через подсрез. Маленький срез от огромного массива держит весь массив в памяти. Нужно скопировать нужное: copy или slices.Clone.