Why must the result of slices.Delete(s, i, j) be assigned back, and what happens to the tail it vacates?
answer
- in place, not a copy
- the header you kept still has the old length
- elements move left over the hole
- the freed tail is set to the zero value
- every aliasing slice watches it happen
basics
~20 sslices.Delete shifts the later elements left inside the same backing array and returns a shorter header, so a variable you never reassign still shows the old length. Since Go 1.22 it also zeroes the slots it vacates.
solid answer
~50 s`slices.Delete(s, i, j)` removes `s[i:j]` by copying the elements after `j` down over them in the *same* backing array, then returns a header whose length is `len(s) - (j-i)`. That header is the only place the new length exists, so unless you write `s = slices.Delete(s, 1, 3)` you keep a view of the old length whose tail is stale. Since Go 1.22 Delete also clears the elements between the new length and the old one, so a deleted pointer or string sitting past the end is no longer reachable through the array and can be collected. Because all of this happens in place, every other slice header over that array sees the shifted, zeroed contents — clone first if the original must survive. `Compact`, `CompactFunc`, `DeleteFunc` and `Replace` shrink and zero the same way.
code
go · 7 liness := []int{10, 20, 30, 40, 50}
t := s // t is a second header over the same array
s = slices.Delete(s, 1, 3) // s is now [10 40 50]
// t still has length 5 and shows [10 40 50 0 0]:
// the elements moved left and the tail was zeroedgo deeper
Remember the assignment: the shrinking helpers in the slices package return a new, shorter slice that you must store back into your variable, exactly as you do with append.
Explain the in-place shift, the returned header's smaller length, and the zeroing of the vacated slots that stops deleted pointers from staying reachable through the array.
In review, spot the aliasing: a slice handed to something else, or sliced out of a larger array, is rewritten by Delete, so clone before deleting when the original must survive.
Decide whether your package mutates a caller's slice at all. An API that deletes in place is fast but surprising, and that decision is very hard to reverse once other teams depend on it.
## Delete works in place and hands you a new header `slices.Delete(s, i, j)` removes the elements `s[i:j]`. It does not allocate. It copies the elements from index `j` onward down into the hole starting at index `i`, inside the **same backing array**, and then returns a slice header whose length is `len(s) - (j - i)`. Because the return value is a *header*, it is the only place the new length exists. Your variable `s` still holds the old header — same pointer, same old length — so if you write ```go slices.Delete(s, 1, 3) // result discarded: a bug ``` you have mutated the array and kept a view of the old length, which now shows the shifted elements followed by whatever the tail contains. The correct form is the same one you already use for `append`: ```go s = slices.Delete(s, 1, 3) ``` `go vet` will not catch the discarded result for you, so this is a review habit. Every `slices` function that changes a slice's length has the same shape: `Insert`, `Delete`, `DeleteFunc`, `Compact`, `CompactFunc`, `Replace`, `Grow`, `Clip`. All of them return the slice you must store back. ## The vacated tail is zeroed Since **Go 1.22**, the shrinking functions clear the array slots between the returned slice's new length and the input slice's old length. `Delete`, `DeleteFunc`, `Compact`, `CompactFunc` and `Replace` all do this. In Go 1.21 they did not, and the stale values simply stayed in the array past the new length. The reason for the change is memory, not tidiness. If the element type contains a pointer — a `*Session`, a `string`, another slice, a struct holding any of those — a copy left sitting in the array past `len` is still reachable from the array, so the garbage collector must keep the object it refers to alive. A long-lived slice used as a queue, shrinking and growing forever, would accumulate exactly that kind of invisible retention. Zeroing the tail drops the reference. It also means the observable contents of the array change beyond the deleted range, which matters the moment anything else is looking at that array. ## Aliasing: who else sees the shift Any other slice header over the same array sees the shifted, zeroed elements — because there is only one array. That includes: - a second variable assigned from the same slice, - a subslice such as `s[2:]` taken earlier, - a slice you returned from a method that exposes a struct field, - a slice stored as a map value that another map got from `maps.Clone`. If the original must survive, take a copy first: `t := slices.Clone(s)`, then delete from whichever of the two you own. That is the entire fix, and it is cheap for small slices. ## The map contrast `maps.DeleteFunc(m, del)` removes every pair for which `del(k, v)` reports true, and it returns **nothing**. That is not an inconsistency — it follows from what the two container values are. A map value is a pointer to a run-time hash table, so a mutation through any copy of that pointer is visible through all of them and there is no new value to hand back. A slice value is a header holding a length, and shrinking it necessarily produces a *different header*, which only the caller can store. Say that out loud in an interview and you have explained Go's value semantics with one example. ## Compact and the "dedupe" trap `slices.Compact(s)` replaces **consecutive** runs of equal elements with a single copy of that element. On `[1 1 2 2 2 3]` it yields `[1 2 3]`. On `[1 2 1 2]` it yields `[1 2 1 2]` — nothing is removed, because no two equal elements are adjacent. Engineers reach for it as a set-dedupe helper and are surprised when duplicates survive; the operation only removes *all* duplicates when equal elements are already adjacent. Like `Delete`, it works in place, returns the shortened slice you must assign back, and zeroes the tail. `slices.CompactFunc` takes an equality function for element types that `==` cannot handle. ## Insert is the one that may reallocate `slices.Insert(s, i, v...)` is the mirror image, with one important difference: it may or may not stay in the original array. If the slice has enough spare capacity, it shifts the tail to the right in place and every alias sees the change; if it does not, it allocates a larger array and copies, and the aliases keep the old one, frozen at the old contents. That "may or may not" is exactly the same non-determinism as `append`, and it is why you cannot reason about aliasing from a single test run. Either way, assign the result back — and if you need a guarantee, clone first. The compact summary: **shrink in place, zero the tail, reassign the header, and remember that everyone else pointing at that array just watched you do it.**
- Why does maps.DeleteFunc return nothing while slices.Delete returns a slice?A map value is a pointer to a run-time hash table, so removing keys through any copy of it is visible through all of them and there is no new value to hand back. A slice value is a header holding a length, and shrinking it necessarily produces a different header, which only the caller can store into the variable.
- Does slices.Compact remove every duplicate from a slice?No — it collapses only runs of consecutive equal elements, so [1 2 1 2] comes back unchanged. Like Delete it works in place, returns the shortened slice you must assign back, and zeroes the tail it vacates. For element types that == cannot handle, slices.CompactFunc takes an equality function.
- How does slices.Insert differ from Delete with respect to the backing array?Insert may reallocate. With enough spare capacity it shifts the tail right in place and every alias sees the change; without it, it allocates a bigger array and copies, leaving the aliases on the old one. That is the same may-or-may-not behaviour as append, and you must assign the result back either way.
saying these in an interview costs you the question
- Calls slices.Delete without assigning the result back
- Thinks Delete allocates a fresh array for you
- Says the deleted values still linger past the length in current Go
- Expects other slices over the same array to be unaffected
- Believes slices.Compact removes non-adjacent duplicates