GoGo example

How to remove an element from a slice in Go

6 min read▶ Runs in an isolated hosted runtimeUpdated Aug 2026

Quick answer

On Go 1.21+, use slices.Delete and assign the result back: s = slices.Delete(s, i, i+1) drops the element at index i. The pre-generics idiom does the same job by hand: s = append(s[:i], s[i+1:]...).

Slices have no Remove method, and nothing in Go shrinks one for you in place — removal is always "copy everything after the hole one position left, then hand back a shorter slice header". That shape explains everything else on this page: why you must assign the result back, why the original slice you passed in is modified too, and why the element you removed can still be sitting in the backing array keeping an object alive. Each example runs on this page — hit Run, then edit the code and run it again.

1slices.Delete (Go 1.21+)Recommended

slices.Delete(s, i, j) removes the half-open range [i, j) and returns the shortened slice, so a single element at index i is Delete(s, i, i+1). The half-open range trips people up once and then never again — it is the same convention as s[i:j], and it means you can drop a whole run in one call. It is generic, so it works on a slice of any element type.

delete.go

Output

Prints [apple cherry date], [10 40 50], then 3 4 — the length dropped to 3 while the capacity stayed at 4, because nothing was reallocated. Two things to hold on to: Delete panics if s[i:j] isn't a valid range of s, so bounds-check an index that came from user input, and the return value is not optional — drop the fruits = and your slice keeps its old length over a rewritten array.

2The classic append idiom

Before Go 1.21, append(s[:i], s[i+1:]...) was the answer, and you will still meet it everywhere. Read it right to left: take the tail after i, spread it with ..., and append it onto the head that stops before i. Both halves point into the same backing array — that's fine, append copies left-to-right — so this is exactly the copy-down slices.Delete performs, written out.

append.go

Output

Prints [apple cherry date], [cherry date], then [apple cherry] — identical to section 1 for the middle element, and the two ends don't need append at all: dropping the first or last element is a pure reslice, O(1) and allocation-free. The one behavioural difference from slices.Delete is invisible here and matters in section 7: this form leaves a stale copy of the removed element in the tail of the backing array.

3Removing by value, not index

Every removal API in Go is index-based, so "delete this value" is two steps: find the index, then delete it. slices.Index returns the position of the first match or -1, and that -1 is the whole reason for the guard — passing it straight to Delete panics. If you only need to know whether the value is there at all, see check if a slice contains an element.

byvalue.go

Output

Prints [apple cherry], mango was not in the slice, then [apple cherry] unchanged. This removes the first match only — for duplicates you would have to loop, which is O(n²) and a classic source of skipped elements. Remove every match in one pass with slices.DeleteFunc instead (section 5). For a custom notion of equality, slices.IndexFunc takes a predicate the same way.

4O(1) removal when order does not matter

Both idioms above shift every later element left, which is O(n). If the slice is really an unordered bag — a worker pool, a free list, a set of active connections — you can move the last element into the hole and cut the length by one. That's two assignments regardless of size, and it scrambles the order, which is exactly the trade you are making.

swap.go

Output

Prints [a e c d] then [d e c]e jumped into slot 1, then d jumped into slot 0. Note it still works when i is the last index: the element is copied onto itself and then sliced away. Like the append idiom this leaves the old value in the tail slot, so for a slice of pointers add s[len(s)-1] = nil before the reslice.

5Removing many elements at once

Deleting inside a loop is the bug factory: every removal shifts the indexes under you, so the straightforward version silently skips elements and runs in O(n²). Do it in one pass instead. slices.DeleteFunc(s, f) (Go 1.21+) removes every element the predicate returns true for; the pre-generics equivalent is the s[:0] filter, which writes the survivors back over the front of the same backing array.

many.go

Output

Prints [1 3 5], [go rust zig], then 3 5 — the filtered slice has three elements and the original capacity of five, proof that it reused the array rather than allocating. Mind the direction of the predicate: DeleteFunc takes a should-remove test, while the loop keeps what you append, so the two conditions are inverses of each other. And the s[:0] trick destroys the input as it goes — if you still need the original order, filter into a fresh make([]T, 0, len(s)).

6Which should you use?

FormOrderCostBest for
slices.Delete(s, i, i+1)PreservedO(n) shiftThe default on Go 1.21+
append(s[:i], s[i+1:]...)PreservedO(n) shiftPre-1.21 code — the same thing by hand
s[i] = s[len(s)-1]; s = s[:len(s)-1]ScrambledO(1)Big slices used as unordered bags
slices.DeleteFunc(s, f)PreservedO(n), one passRemoving every element matching a rule
s[:len(s)-1] or s[1:]PreservedO(1)Dropping the last or first element
slices.Delete(slices.Clone(s), …)PreservedO(n) + one allocLeaving the caller’s slice untouched

7The trap: aliasing and the elements left behind

A slice is a header — pointer, length, capacity — over an array it doesn't own exclusively. Removing an element rewrites that array, so every other slice sharing it sees the new contents, including the caller's. And the vacated slot at the end still holds whatever was there: for a []*User or [][]byte that's a live reference the garbage collector cannot free. Since Go 1.22 slices.Delete zeroes the vacated slots for you; the hand-rolled idioms do not, and clear is the fix.

aliasing.go

Output

The first line prints [1 3 4 0] 4: nums was never reassigned, so it still has length 4, but its contents were shifted left and the freed slot zeroed — the caller's data changed even though the return value was thrown away. Line two prints ["ann" "cy"] ["ann" "cy" "cy"]: the visible slice is right, but the full backing array still holds a second "cy" past the end. After clear it reads ["ann" "cy" ""]. For value types the leftovers are harmless; for pointers, interfaces, or anything holding a big buffer they pin memory for as long as the slice lives. When the caller must not see the change at all, remove from slices.Clone(s) — the same in-place hazard as sorting a slice.

Frequently asked questions

How do I remove an element from a slice in Go?

On Go 1.21+, call s = slices.Delete(s, i, i+1) to remove the element at index i. On older versions, use s = append(s[:i], s[i+1:]...), which does the same shift by hand. Either way you must assign the result back — nothing in Go shrinks a slice in place.

What does slices.Delete return, and why must I reassign it?

It returns a new slice header with a shorter length over the same backing array. The array is modified in place, but your original variable still has the old length, so ignoring the return value leaves you looking at a shifted slice with a duplicate at the end. Always write s = slices.Delete(s, i, j).

How do I remove an element from a slice by value in Go?

Find the index first: if i := slices.Index(s, v); i >= 0 { s = slices.Delete(s, i, i+1) }. slices.Index returns -1 when the value is absent, and passing -1 to Delete panics, so the guard is required. That removes only the first match — use slices.DeleteFunc to remove every occurrence in one pass.

How do I remove multiple elements from a slice in Go?

Use slices.DeleteFunc(s, func(v T) bool { … }), which removes every element the predicate returns true for in a single O(n) pass. The pre-generics version of the same trick is the filter idiom: keep := s[:0] then append the survivors. Do not delete inside a loop over the same slice — the indexes shift under you and you skip elements.

Does removing an element from a slice modify the original slice?

Yes. A slice is a header over a shared backing array, so the shift is visible through every slice pointing at that array, including the one the caller passed in. If the caller must keep its data, remove from a copy: slices.Delete(slices.Clone(s), i, i+1).

Does removing an element from a slice cause a memory leak in Go?

It can. The elements past the new length still live in the backing array, so a removed pointer, interface, or byte slice stays reachable and cannot be collected. Since Go 1.22 slices.Delete zeroes the vacated slots for you; if you use append(s[:i], s[i+1:]...) or the swap-with-last trick, zero them yourself with clear(s[len(s):cap(s)]) or s[len(s)-1] = nil.

What is the fastest way to remove an element from a large Go slice?

If order does not matter, move the last element into the gap and reslice: s[i] = s[len(s)-1]; s = s[:len(s)-1]. That is O(1) instead of the O(n) shift that slices.Delete and the append idiom perform. Dropping the first or last element is also O(1) — s[1:] and s[:len(s)-1] are pure reslices.