Question
I want to combine the slice []int{1, 2} with the slice []int{3, 4} in Go.
I tried this:
append([]int{1, 2}, []int{3, 4})
but I got this error:
cannot use []int literal (type []int) as type int in append
The documentation seems to suggest this should be possible. What am I missing?
I also saw this form in the documentation:
slice = append(slice, anotherSlice...)
Why does that version work?
Short Answer
By the end of this page, you will understand how Go's append function works, why appending one slice to another requires the ... operator, and how to safely combine slices in real programs.
Concept
In Go, a slice is a dynamic view over an underlying array. The built-in append function is used to add elements to a slice.
The important detail is this:
append(slice, elements...)
append expects individual elements of the slice's element type after the first argument.
For example, if you have a []int, then append expects one or more int values:
nums := []int{1, 2}
nums = append(nums, 3, 4)
That works because 3 and 4 are int values.
But this does not work:
append([]int{1, 2}, []int{3, 4})
because the second argument is a whole []int slice, not individual int values.
To tell Go to treat the second slice as a list of elements, you use the variadic expansion operator ...:
append([]int{1, 2}, []int{3, 4}...)
This expands the second slice so that each element is passed separately.
This matters in real programming because slices are one of the most common data structures in Go. You often need to merge results, build lists dynamically, collect filtered data, or combine pieces of input. Understanding append correctly helps you write clear and efficient Go code.
Mental Model
Think of append like adding items to a shopping basket.
append(basket, apple, banana)means: add these individual items.append(basket, anotherBasket)means: try to add a whole basket as if it were a single item.append(basket, anotherBasket...)means: open the second basket and pour all its items into the first one.
The ... operator is the “unpack everything inside” instruction.
Syntax and Examples
Core syntax
result := append(slice, element1, element2)
result := append(slice, anotherSlice...)
Example: append individual values
package main
import "fmt"
func main() {
nums := []int{1, 2}
nums = append(nums, 3, 4)
fmt.Println(nums)
}
Output:
[1 2 3 4]
Here, 3 and 4 are individual int values.
Example: concatenate two slices
package main
import "fmt"
func main() {
a := []int{1, 2}
b := []int{3, 4}
result := append(a, b...)
fmt.Println(result)
}
Output:
[1 2 3 4]
Here, b... expands b into individual elements.
Example: creating a new combined slice
package main
import "fmt"
func main() {
a := []int{1, 2}
b := []int{3, 4}
combined := append([]int{}, a...)
combined = append(combined, b...)
fmt.Println(combined)
}
Output:
[1 2 3 4]
This version is useful when you want a new slice instead of reusing a.
Step by Step Execution
Consider this code:
package main
import "fmt"
func main() {
a := []int{1, 2}
b := []int{3, 4}
result := append(a, b...)
fmt.Println(result)
}
Step-by-step
a := []int{1, 2}creates a slice with two integers.b := []int{3, 4}creates another slice with two integers.append(a, b...)starts withaas the destination slice.b...expands into the values3, 4.- Go treats the call like this conceptually:
append(a, 3, 4)
- The new elements are added to the end of
a. - The returned slice is assigned to
result. fmt.Println(result)prints:
[1 2 3 4]
Important detail
append returns a slice. You should usually assign the result:
a = append(a, b...)
This matters because append may allocate a new underlying array if the original slice does not have enough capacity.
Real World Use Cases
Slices are used everywhere in Go, so concatenation comes up often.
Common use cases
- Combining API results: merge data from multiple pages of an API response.
- Building filtered lists: append matched items into a result slice.
- Collecting logs or events: combine batches before processing.
- Reading files or network chunks: join pieces of data into one slice.
- Middleware and routing: combine lists of handlers or configuration values.
Example: merging paginated results
allUsers := []string{"alice", "bob"}
nextPageUsers := []string{"carol", "dave"}
allUsers = append(allUsers, nextPageUsers...)
Example: collecting valid values
numbers := []int{1, 2}
moreNumbers := []int{3, 4, 5}
numbers = append(numbers, moreNumbers...)
This pattern is simple but very common in production code.
Real Codebase Usage
In real Go projects, developers use append in a few common ways.
1. Building result slices
var result []int
for _, n := range []int{1, 2, 3, 4} {
if n%2 == 0 {
result = append(result, n)
}
}
This is a standard way to filter data.
2. Merging slices from multiple sources
combined := append(configDefaults, envOverrides...)
combined = append(combined, cliArgs...)
This is useful for configuration and layered inputs.
3. Copying before appending
safe := append([]string{}, original...)
safe = append(safe, "new-item")
Developers do this when they do not want to modify a slice that might be shared.
4. Guarding against unintended mutation
Even though append returns a new slice value, it may still reuse the original backing array. In shared code, developers sometimes create a fresh slice first to avoid accidental side effects.
5. Working with byte slices
data := []byte("Hello, ")
data = append(data, []byte("world")...)
This is common when building buffers, network payloads, or file content.
Common Mistakes
1. Forgetting ... when appending a slice
Broken code:
append([]int{1, 2}, []int{3, 4})
Why it fails:
appendexpectsintvalues after the first argument.[]int{3, 4}is a slice, not individualintvalues.
Correct code:
append([]int{1, 2}, []int{3, 4}...)
2. Forgetting to assign the result of append
Broken code:
a := []int{1, 2}
append(a, 3, 4)
fmt.Println(a)
Why it is wrong:
appendreturns the updated slice.- If you ignore the result, your variable may still refer to the old slice.
Correct code:
a := []int{1, 2}
a = append(a, 3, 4)
fmt.Println(a)
3. Assuming append always creates a new slice
Some beginners think append always copies data. It may reuse the same backing array if there is enough capacity.
If you need a guaranteed separate slice, copy first:
newSlice := append([]int{}, oldSlice...)
4. Mixing element types
Broken code:
append([]int{1, 2}, "3")
Why it fails:
- The slice stores
intvalues. "3"is a string, not anint.
5. Confusing arrays and slices
An array like [2]int{1, 2} is not the same as a slice like []int{1, 2}. append works with slices, not fixed-size arrays directly.
Comparisons
| Concept | What it means | Example | When to use |
|---|---|---|---|
append(slice, x) | Append one element | append(a, 3) | Add a single value |
append(slice, x, y) | Append multiple elements | append(a, 3, 4) | Add several known values |
append(slice, anotherSlice...) | Append all elements from another slice | append(a, b...) | Concatenate slices |
copy(dst, src) | Copy elements from one slice into another | copy(dst, src) | Duplicate data into an existing slice |
append vs copy
appendgrows a slice by adding elements to the end.copycopies elements into a destination slice that already exists.
Example:
a := []int{1, 2}
b := []int{3, 4}
c := append(a, b...)
fmt.Println(c) // [1 2 3 4]
d := make([]int, len(b))
copy(d, b)
fmt.Println(d) // [3 4]
Slice literal vs variadic expansion
| Form | Meaning |
|---|---|
[]int{3, 4} | A single slice value |
[]int{3, 4}... | Expand the slice into separate int arguments |
That difference is the key to understanding the original error.
Cheat Sheet
Quick reference
Append single values
s = append(s, 1)
s = append(s, 2, 3)
Append one slice to another
s = append(s, other...)
Create a new combined slice
combined := append([]int{}, a...)
combined = append(combined, b...)
Important rules
appendworks with slices.appendreturns the updated slice.- Use
...to expand a slice into individual arguments. - Without
..., Go sees the second slice as one value, not many elements. appendmay reuse the original backing array.
Common error
append([]int{1, 2}, []int{3, 4})
Error reason:
appendexpectsint, but got[]int.
Correct version:
append([]int{1, 2}, []int{3, 4}...)
FAQ
Why do I need ... when appending a slice in Go?
Because append expects individual elements, not a slice value. ... expands the slice into separate arguments.
Can I append one slice directly to another without ...?
No. Not with append. Without ..., Go treats the second slice as a single argument of type []int, which does not match the expected element type.
Does append modify the original slice?
It returns an updated slice. It may reuse the same underlying array or allocate a new one, depending on capacity.
What is the difference between append(a, b...) and append(a, 3, 4)?
If b is []int{3, 4}, then they are conceptually the same. b... expands into the individual values.
Can I concatenate slices of different types in Go?
No. The element types must match. For example, []int can only be appended with int values or another []int expanded with ....
How do I avoid changing the original slice when concatenating?
Create a new slice first:
combined := append([]int{}, a...)
combined = append(combined, b...)
Does append work with arrays?
Not directly. append works with slices. You can slice an array first, such as arr[:].
Mini Project
Description
Build a small Go program that merges two lists of task IDs into one combined list. This demonstrates how slice concatenation works in a realistic scenario where data may come from two different sources, such as a database query and an API response.
Goal
Create a program that combines two []int slices into a single result slice and prints the merged output.
Requirements
- Create two integer slices with sample task IDs.
- Combine the slices using
appendand the...operator. - Print the final combined slice.
- Also create a second version that preserves the original first slice by copying it first.
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.