JavaRush /Courses /Go SELF /slices package: Delete, Insert, Clone

slices package: Delete, Insert, Clone

Go SELF
Level 13, Lesson 5
Available

1. Why the slices package is great

When you first learn to delete a chunk from a slice, the recipe using append(s[:i], s[j:]...) feels like magic: “how does this even work and why the three dots?!”. Later you get used to it, but the magic doesn’t disappear — it just moves from “I don’t get it” to “autopilot”. And autopilot is exactly where bugs are born: mixed up the bounds, forgot to assign the result, accidentally overwrote data.

The slices package is an attempt to make code more self-documenting: not “here’s a trick with append”, but “here’s Delete — delete a range”. These functions work with slices of any type (thanks to generics), and it’s important to be able to read and apply them correctly, even if you’re not planning to write your own generic utilities.

To lock in the idea, let’s look at the same operation in two styles:

What we want to do “Hand-rolled” recipe Standard via slices
Delete a range [i:j)
s = append(s[:i], s[j:]...)
s = slices.Delete(s, i, j)
Insert elements at position i
append + copy + ...
s = slices.Insert(s, i, values...)
Make an independent copy
b := make([]T, len(a)); copy(b, a)
b := slices.Clone(a)

The point isn’t that the standard library is “smarter than you”. The point is that it reduces cognitive load: fewer “tricks”, more “intent”.

2. The main rule: if the length changes, save the result

The slices package is designed specifically to mirror the familiar append model. If an operation can change len (and sometimes reallocate the backing array), it returns a new slice, and you must save it. That’s why slices.Delete and slices.Insert return a value.

This leads to a simple rule that saves hours of your life: if a function changes the length, write s = …. Not for aesthetics, but because otherwise your variable will keep the old length, while the backing array contents may already be partially overwritten (meaning you’ll get a “zombie slice”: it looks alive, but inside everything is different).

Mini demo of a “zombie slice”:

package main

import (
	"fmt"
	"slices"
)

func main() {
	s := []int{1, 2, 3, 4, 5}

	slices.Delete(s, 1, 4) // forgot to assign the result
	fmt.Println(s)         // the contents may become surprising
}

This program compiles, runs, sometimes even “looks like it works” — and that’s exactly why it’s dangerous.

3. Practice: a todo list on []string

So the examples aren’t “about spherical horses in a vacuum”, we’ll build a tiny console core: a todo list, where each item is just a string. Yes, no structs, no database, and no microservices — today is a celebration of simplicity.

The idea is: we have tasks := []string{…}, and we want to be able to:

  1. safely copy the list (for example, before experiments),
  2. delete a single task or a range,
  3. insert tasks into the middle (for example, urgent tasks).

slices.Clone: “copy for real”

A very common beginner mistake with slices sounds like this: “I copied a slice b := a, then changed b, and why did a change?”. Answer: because you copied the slice header, not the data. Both slices share the same backing array — meaning you didn’t make a copy of the list, you made a second window into the same data.

Clone solves exactly this: create a new backing array and move the elements into it.

Example: make a draft without touching the original.

package main

import (
	"fmt"
	"slices"
)

func main() {
	tasks := []string{"buy milk", "read book", "walk dog"}
	draft := slices.Clone(tasks)

	draft[0] = "buy coffee"
	fmt.Println(tasks) // [buy milk read book walk dog]
	fmt.Println(draft) // [buy coffee read book walk dog]
}

Here draft is an independent copy: change it however you want, and tasks won’t suffer.

A slightly more realistic scenario: suppose you want to “try” filtering or deletion, but you’re not sure you computed the indexes correctly. You can clone, experiment, and only then apply it to the original (or abandon it entirely). In real code you don’t always need to do this (memory isn’t free), but as a safety technique for beginners it’s very useful.

slices.Delete: delete a range [i:j)

When we delete from a slice, we’re really doing two things: shifting the tail into the “hole” and reducing the length. We used to write this by hand. Now we can say it directly: Delete. And it reads like an English sentence: “delete from i to j”.

It’s important to remember the range is half-open: [i:j) means i is included, but j is not. It’s the same model as s[i:j].

Example: delete one task by index i.

package main

import (
	"fmt"
	"slices"
)

func main() {
	tasks := []string{"buy milk", "read book", "walk dog"}

	i := 1
	tasks = slices.Delete(tasks, i, i+1)

	fmt.Println(tasks) // [buy milk walk dog]
}

Example: delete a range (for example, “wipe a block of tasks” that’s no longer relevant).

package main

import (
	"fmt"
	"slices"
)

func main() {
	tasks := []string{"A", "B", "C", "D", "E"}

	tasks = slices.Delete(tasks, 1, 4)
	fmt.Println(tasks) // [A E]
}

After Delete, it’s better not to treat the old slice as “stable”

Delete typically shifts elements in place (meaning the backing array is reused if no reallocation is needed). So after a mutating operation, it’s better to treat the original slice as “invalid” in the sense of assumptions about its length/contents, if you ignore the returned value or keep using old “views” of it.

A bad idea looks like: “I’ll do u := slices.Delete(s, …), and then I’ll keep actively using s as if nothing happened”. This is especially dangerous if there are subslices nearby that share the same backing array.

About the tail and memory: Delete tries to clear freed elements

Historically there was a subtle problem: after deletion, elements could remain in the tail of the backing array (beyond the new len) and keep memory alive if they contained references. Starting with Go 1.22, the behavior of a number of functions in the slices package was changed toward safer handling of that tail (including zeroing freed elements).

That’s good news, but it doesn’t replace discipline: you still have to save the result, and you still have to compute indexes correctly.

slices.Insert: insert elements at position i

Insertion is the symmetric operation to deletion. If deletion “collapses a hole”, insertion “creates a hole”: it shifts the tail to the right and increases len. We used to do this with append + copy. Now we can express the intent directly: Insert.

Insert takes a position and variadic arguments (the elements to insert). It returns a new slice because the length changes, and the backing array may be reallocated.

Example: insert one urgent task at the beginning.

package main

import (
	"fmt"
	"slices"
)

func main() {
	tasks := []string{"read book", "walk dog"}

	tasks = slices.Insert(tasks, 0, "pay rent")
	fmt.Println(tasks) // [pay rent read book walk dog]
}

Example: insert several tasks into the middle at once.

package main

import (
	"fmt"
	"slices"
)

func main() {
	tasks := []string{"A", "D"}

	tasks = slices.Insert(tasks, 1, "B", "C")
	fmt.Println(tasks) // [A B C D]
}

How to read this: “insert into tasks at position 1 the elements "B" and "C"”. No copy, no manual growth, no risk of mixing up i+k.

Three rules for safe use of Delete/Insert/Clone

When you first start using slices, you might get the feeling: “yay, now everything is safe and it’s impossible to mess up”. Unfortunately, you can always mess up. But you can greatly reduce the risk if you keep three rules in mind.

Rule one — always save the returned value if the function changes the length. For Delete and Insert, this isn’t style, it’s correctness.

Rule two — be careful with indexes and half-open intervals. Delete(s, i, j) deletes exactly [i:j). If on autopilot you think “inclusive”, you’ll delete one element less or more. And “one element” is programming’s favorite size of disaster.

Rule three — don’t multiply “old versions” of a slice and hope they still reflect reality. If you had subslices and you performed Delete/Insert on the original, think about what happens to the subslices now. The backing array may have shifted, elements may have moved, and the tail may have been zeroed.

Mini refactor: functions for the todo core

Now we’ll assemble a small set of functions around []string so the code in main looks human. This is good style: wrap data operations into separate functions, and in main just show a scenario.

Remove a task by index, with protection against an invalid i

package main

import "slices"

func removeTask(tasks []string, i int) []string {
	if i < 0 || i >= len(tasks) {
		return tasks
	}
	return slices.Delete(tasks, i, i+1)
}

Notice how the last line reads: “delete from i to i+1”. That is, one element.

Insert a task “after i”

package main

import "slices"

func insertAfter(tasks []string, i int, task string) []string {
	pos := i + 1
	if pos < 0 || pos > len(tasks) {
		return tasks
	}
	return slices.Insert(tasks, pos, task)
}

We deliberately allow pos == len(tasks) — that means “insert at the end”, and that’s valid.

Draft (clone) before an experiment

package main

import "slices"

func draftCopy(tasks []string) []string {
	return slices.Clone(tasks)
}

Minimalistic and honest in meaning.

Scenario in main

package main

import "fmt"

func main() {
	tasks := []string{"A", "B", "C"}
	copyForTest := draftCopy(tasks)

	tasks = removeTask(tasks, 1)
	tasks = insertAfter(tasks, 0, "X")

	fmt.Println("tasks:", tasks)       // tasks: [A X C]
	fmt.Println("draft:", copyForTest) // draft: [A B C]
}

Here it’s immediately clear that Clone really protects the original: the draft stayed unchanged.

slices as a shared vocabulary on a team

There’s a non-obvious but very practical advantage of standard functions: they establish a shared vocabulary. When you see slices.Delete(s, 2, 5), you barely think about the mechanics — you read the intent.

This is especially important on a team where people have different levels of experience: someone “juggles append with their eyes closed”, while someone else is still confusing [i:j).

4. Typical mistakes when working with slices.Delete/Insert/Clone

Error #1: call slices.Delete or slices.Insert and don’t assign the result back.
This is the most popular bug because the code compiles and looks plausible. The problem is that the length in variable s doesn’t change, while the backing array may already be partially overwritten. The right habit: if the operation changes the length, write s = slices.Delete(...) and s = slices.Insert(...).

Error #2: accidentally shadow via := instead of =.
A classic: inside an if or for you write s := slices.Delete(s, 2, 3), thinking you updated s, but you actually created a new variable s only inside the block. Outside, everything stays old, and then you stare at the output for a long time, suspecting Go is “joking”.

Error #3: mix up the bounds and think j is included.
In Delete(s, i, j), the deleted range is [i:j); the right bound is excluded. If you want to delete one element at i, write Delete(s, i, i+1). If you want to delete “inclusive”, you need to convert it to a half-open interval yourself (usually j+1), and watch carefully not to go past len.

Error #4: keep actively using old subslices after Delete/Insert.
If somewhere you had head := s[:k] or tail := s[k:], and then you did s = slices.Delete(s, ...), then head and tail may suddenly start showing unexpected data because the backing array was reused, elements were shifted, and the tail may have been cleared. That’s not “magic”, it’s the aliasing model: multiple slices can share one backing array.

Error #5: think slices.Clone is the same as b := a.
b := a copies only the “header” (pointer/len/cap), not the elements. slices.Clone(a) makes an independent copy of the data by allocating a new backing array.

1
Task
Go SELF, level 13, lesson 5
Locked
Playlist clone
Playlist clone
1
Task
Go SELF, level 13, lesson 5
Locked
Delete a complaint
Delete a complaint
1
Task
Go SELF, level 13, lesson 5
Locked
Insert Phrases
Insert Phrases
1
Task
Go SELF, level 13, lesson 5
Locked
Command editor
Command editor
1
Survey/quiz
Slice operations, 13 level, 5 lesson
Unavailable
Slice operations
Slice operations
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION