Why Go’s slices.Move Beats Delete+Insert for Single‑Element Relocation
The article examines a Go TODO app where moving a task required a Delete‑then‑Insert pattern, highlights the inefficiency of two memory moves, introduces the slices.Move proposal that performs a single copy, reviews community arguments, and makes a case for adding Move to the standard library.
One Afternoon of "Clumsy Code"
While building a TODO application last year, I needed to let users drag a task to a new priority, which required re‑ordering a slice of []Task sorted by priority. Changing a task from low to high priority should move it from the end of the slice to near the front.
The initial implementation looked like this:
// Assume tasks is a priority‑sorted slice
oldIdx := findTaskIndex(tasks, taskID)
task := tasks[oldIdx]
task.Priority = newPriority
// Delete old position
tasks = slices.Delete(tasks, oldIdx, oldIdx+1)
// Find new position and insert
newIdx := findNewPosition(tasks, task)
tasks = slices.Insert(tasks, newIdx, task)Although it works, the code feels wrong because it performs two memory moves: one for Delete and another for Insert, plus a slice length change.
I wondered why the Go standard library provides slices.Delete and slices.Insert but lacks a function that moves an element directly from index A to index B.
Then I discovered a proposal submitted on 2026‑05‑31 by developer abemedia to add slices.Move to the slices package.
Current Approach (Delete + Insert)
// Move element: from index "from" to "to"
v := s[from]
s = slices.Delete(s, from, from+1) // first memory move
s = slices.Insert(s, to, v) // second memory moveProposed Approach (Move)
func Move[S ~[]E, E any](s S, from, to int) {
if from == to {
return
}
v := s[from]
if from < to {
copy(s[from:to], s[from+1:to+1]) // shift left
} else {
copy(s[to+1:from+1], s[to:from]) // shift right
}
s[to] = v
}The core optimisation is that the element is moved with a single copy operation instead of two.
Usage Example
// When a key in a sorted slice changes, reposition the element
oldIdx, _ := slices.BinarySearchFunc(records, r, byPK)
r.PK = newValue
newIdx, _ := slices.BinarySearchFunc(records, r, byPK)
if newIdx > oldIdx {
newIdx--
}
slices.Move(records, oldIdx, newIdx) // one‑line, single memory moveThe discussion around the proposal reveals several viewpoints:
"Moving a single element costs O(n); that usually means you chose the wrong data structure." – Core team member questioning the need for Move.
This reflects a theoretical stance that slices are unsuitable for frequent element moves.
"slices.Insert and slices.Delete are already O(n). If O(n) is a problem, those functions shouldn’t exist either. Move simply fills the gap." – Proposal author rebuttal.
Another comment argues that in UI lists, small‑scale LRU caches, and similar scenarios, slices provide excellent cache locality, making the practical performance advantage outweigh the algorithmic cost.
Some suggest using slices.Rotate instead of Move, but the author finds Move more direct and intuitive.
In the original TODO app, the list usually contains only dozens to a few hundred tasks, so Delete+Insert works but feels like technical debt: three lines of code, careful slice length handling, and difficulty when additional logic (logging, validation) is inserted between Delete and Insert.
With slices.Move, the same operation becomes a single clear statement, improving readability and roughly doubling performance for small slices.
When Go 1.21 introduced the slices package, the designers deliberately kept the API minimal, adding only the most common operations. Insert and Delete were accepted because they cover typical slice modifications, while Move was omitted, partly due to concerns that developers might overuse it and incur performance problems on large data.
The proposal demonstrates that engineering needs can sometimes outweigh theoretical purity: a well‑defined, frequently‑used pattern deserves a dedicated primitive.
Three reasons are given for adding slices.Move to the standard library:
Fill an API gap : With Delete and Insert already present, a Move operation naturally completes the set.
Clear performance benefit : One memory copy versus two, which matters in performance‑sensitive code.
Improved code readability : slices.Move(s, from, to) expresses intent unmistakably.
Although the core team’s concern about algorithmic complexity is valid, Go’s philosophy has always valued practicality. Providing slices.Move aligns with that pragmatic approach and gives developers a clearer, faster way to reorder small slices.
After the proposal is accepted, I plan to refactor the TODO app, replacing the three‑line Delete+Insert pattern with a single slices.Move call, achieving the simplicity and efficiency that Go strives for.
Source: Huawei Cloud Community, golang学习记 – https://bbs.huaweicloud.com/blogs/479075
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Golang Shines
We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
