Question
I want to delete an element from a slice in Go based on its position.
Here is my current code:
fmt.Println("Enter position to delete:")
fmt.Scanln(&pos)
newArr := make([]int, len(arr)-1)
k := 0
for i := 0; i < len(arr)-1; {
if i != pos {
newArr[i] = arr[k]
k++
i++
} else {
k++
}
}
for i := 0; i < len(arr)-1; i++ {
fmt.Println(newArr[i])
}
This code is intended to remove one element from the slice, but it does not behave correctly. What is the proper way to delete an element from a slice in Go?
Short Answer
By the end of this page, you will understand how slice deletion works in Go, why slices cannot remove elements directly, and how to create a new slice without the unwanted item using idiomatic Go code such as append(slice[:i], slice[i+1:]...). You will also learn how to handle index validation and avoid common off-by-one mistakes.
Concept
In Go, a slice is a flexible view over an underlying array. Unlike some other languages, slices do not have a built-in delete operation for removing an element by index. Instead, you create a new slice that skips the element you do not want.
The most common and idiomatic approach is:
arr = append(arr[:pos], arr[pos+1:]...)
This works by combining:
arr[:pos]→ everything before the elementarr[pos+1:]→ everything after the elementappend(...)→ joins those two parts together
Why this matters:
- Slices are used everywhere in Go for lists of data.
- Real programs often need to remove items from memory, user input, API results, or filtered data.
- Understanding slice deletion helps you write simpler and safer code.
A key point: deleting from a slice does not shrink an array in place. It creates a new slice value, often reusing the same underlying array.
You should also validate the index before deleting, because using an invalid position will cause a runtime panic.
Mental Model
Think of a slice like a train made of connected carriages.
If you want to remove one carriage in the middle, you do not "delete" it magically. Instead, you reconnect:
- the carriages before it
- to the carriages after it
So if the slice is:
[10, 20, 30, 40, 50]
and you remove index 2 (30), you reconnect:
[10, 20]- with
[40, 50]
resulting in:
[10, 20, 40, 50]
That is exactly what append(arr[:pos], arr[pos+1:]...) does.
Syntax and Examples
Core syntax
slice = append(slice[:index], slice[index+1:]...)
Example: remove an element by index
package main
import "fmt"
func main() {
arr := []int{10, 20, 30, 40, 50}
pos := 2
if pos < 0 || pos >= len(arr) {
fmt.Println("Invalid position")
return
}
arr = append(arr[:pos], arr[pos+1:]...)
fmt.Println(arr)
}
Output:
[10 20 40 50]
What this does
arr[:pos]gives all elements beforeposarr[pos+1:]gives all elements afterpos...expands the second slice into individual elements forappend
Example with user input
package main
import "fmt"
func main() {
arr := []int{1, 2, 3, 4, 5}
var pos int
fmt.Print("Enter position to delete: ")
fmt.Scanln(&pos)
if pos < 0 || pos >= len(arr) {
fmt.Println("Invalid position")
return
}
arr = append(arr[:pos], arr[pos+1:]...)
fmt.Println("Updated slice:", arr)
}
This is shorter, clearer, and less error-prone than manually copying elements with two counters.
Step by Step Execution
Consider this code:
arr := []int{10, 20, 30, 40, 50}
pos := 2
arr = append(arr[:pos], arr[pos+1:]...)
fmt.Println(arr)
Step 1: Original slice
[10, 20, 30, 40, 50]
Indexes are:
| Index | Value |
|---|---|
| 0 | 10 |
| 1 | 20 |
| 2 | 30 |
| 3 | 40 |
| 4 | 50 |
Step 2: Take elements before pos
arr[:2]
Result:
[10, 20]
Step 3: Take elements after pos
arr[3:]
Result:
[40, 50]
Step 4: Append the second part to the first
append([]int{10, 20}, 40, 50)
Result:
[10, 20, 40, 50]
Step 5: Store the result back
arr = [10, 20, 40, 50]
Final output
[10 20 40 50]
The original element at index 2 is skipped, so it is effectively removed.
Real World Use Cases
Deleting an element from a slice is common in many Go programs.
Common scenarios
- Removing a completed task from a todo list
- Deleting a user-selected item from a CLI menu
- Dropping invalid records from parsed data
- Removing an item from an in-memory cache
- Excluding a failed job from a processing queue
Example: remove a product from a cart
cart := []string{"book", "pen", "bag"}
removeIndex := 1
cart = append(cart[:removeIndex], cart[removeIndex+1:]...)
fmt.Println(cart)
Output:
[book bag]
Example: remove a bad value after validation
scores := []int{90, -1, 75, 88}
badIndex := 1
scores = append(scores[:badIndex], scores[badIndex+1:]...)
fmt.Println(scores)
This pattern is useful whenever your program keeps data in memory and needs to exclude one entry.
Real Codebase Usage
In real projects, developers usually wrap slice deletion in a helper function and combine it with validation.
Common pattern: helper function
func removeAt(slice []int, index int) []int {
if index < 0 || index >= len(slice) {
return slice
}
return append(slice[:index], slice[index+1:]...)
}
This makes the code reusable and safer.
Guard clause pattern
Developers often check invalid conditions early:
if pos < 0 || pos >= len(arr) {
return
}
This avoids deeply nested logic and prevents runtime errors.
Filtering instead of deleting one index
Sometimes developers do not know the index ahead of time. In that case, they build a new slice using a condition:
result := arr[:0]
for _, v := range arr {
if v != 30 {
result = append(result, v)
}
}
arr = result
This is common when removing values based on rules rather than position.
Error handling with user input
In command-line tools or APIs, index values often come from users or requests. Real code should validate input before slicing to avoid panics.
Common Mistakes
1. Not checking the index range
If pos is invalid, the program panics.
Broken code:
arr = append(arr[:pos], arr[pos+1:]...)
Safer version:
if pos < 0 || pos >= len(arr) {
fmt.Println("Invalid position")
return
}
arr = append(arr[:pos], arr[pos+1:]...)
2. Forgetting the ...
Broken code:
arr = append(arr[:pos], arr[pos+1:])
Why it fails:
appendexpects values to appendarr[pos+1:]is a slice...expands the slice into values
Correct code:
arr = append(arr[:pos], arr[pos+1:]...)
3. Mixing indexes while manually copying
Your original code uses both i and k, which makes the logic harder to follow and easier to break.
Broken style:
for i := 0; i < len(arr)-1; {
if i != pos {
newArr[i] = arr[k]
k++
i++
} else {
k++
}
}
This can lead to incorrect copying or skipped values.
4. Assuming slices behave like dynamic arrays with built-in deletion
Go slices are flexible, but deletion is done by reslicing and appending, not by calling something like delete(slice, index).
5. Forgetting that order matters
The standard append(slice[:i], slice[i+1:]...) keeps order.
If order does not matter, some programs use a faster approach by replacing the target with the last element:
arr[pos] = arr[len(arr)-1]
arr = arr[:len(arr)-1]
But this changes the order of elements.
Comparisons
Slice deletion approaches
| Approach | Keeps order? | Easy to read? | Typical use |
|---|---|---|---|
append(slice[:i], slice[i+1:]...) | Yes | Yes | Standard deletion by index |
| Manual copy into new slice | Yes | No | Rarely needed for beginners |
| Replace with last element, then shorten | No | Yes | Faster removal when order does not matter |
Standard removal vs swap-with-last
Standard removal
arr = append(arr[:pos], arr[pos+1:]...)
- Preserves original order
- Most common solution
- Best default choice
Swap with last
arr[pos] = arr[len(arr)-1]
arr = arr[:len(arr)-1]
- Faster in some cases
- Does not preserve order
- Useful in performance-sensitive code
Slice vs array in Go
| Feature | Slice | Array |
|---|---|---|
| Flexible length view | Yes | No |
| Commonly used in Go | Yes | Less often |
| Easy reslicing | Yes | No |
| Can use append | Yes | No |
For most Go programs, you will work with slices rather than arrays.
Cheat Sheet
Delete by index
slice = append(slice[:i], slice[i+1:]...)
Safe version
if i >= 0 && i < len(slice) {
slice = append(slice[:i], slice[i+1:]...)
}
Remove last element
slice = slice[:len(slice)-1]
Remove first element
slice = slice[1:]
Fast removal when order does not matter
slice[i] = slice[len(slice)-1]
slice = slice[:len(slice)-1]
Rules to remember
- Always validate the index
- Use
...when appending a slice into another slice append(slice[:i], slice[i+1:]...)preserves order- Slices do not have a built-in
deleteby index - Removing an item changes the slice length
Common edge cases
i < 0→ invalidi >= len(slice)→ invalid- empty slice → cannot remove anything
- removing the last item works with
slice[:len(slice)-1]or the standard pattern
FAQ
How do I remove an element from a slice in Go?
Use:
slice = append(slice[:i], slice[i+1:]...)
This removes the element at index i.
Why is there no delete for slices in Go?
Because slices are views over arrays. Go expects you to build a new slice layout using slicing and append.
Do I need to create a new slice manually?
Usually no. The idiomatic append(slice[:i], slice[i+1:]...) is enough.
What happens if the index is out of range?
Your program will panic at runtime. Always check that i >= 0 && i < len(slice).
Does deleting from a slice preserve order?
Yes, the standard append(slice[:i], slice[i+1:]...) approach preserves the order of the remaining elements.
Is there a faster way if I do not care about order?
Yes:
slice[i] = slice[len(slice)-1]
slice = slice[:len(slice)-1]
This is useful when performance matters more than element order.
Mini Project
Description
Build a small Go program that removes an item from a list of numbers based on a position entered by the user. This project demonstrates safe slice deletion, index validation, and printing updated results. It is a practical version of what many command-line tools do when modifying in-memory data.
Goal
Create a Go program that reads a position, removes that element from a slice safely, and prints the updated slice.
Requirements
- Start with a slice containing at least five integers.
- Read the position to delete from user input.
- Validate that the position is within range.
- Remove the element using idiomatic slice syntax.
- Print the updated slice after deletion.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.