Question
I am trying to combine multiple strings into one comma-separated string in Go using strings.Join, but my code gives a type error:
package main
import (
"fmt"
"strings"
)
func main() {
reg := [...]string{"a", "b", "c"}
fmt.Println(strings.Join(reg, ","))
}
The error is:
prog.go:10: cannot use reg (type [3]string) as type []string in argument to strings.Join
Why does this happen, and what is the correct way to join these values into a single string? Is there a better approach than manually looping and appending to a variable?
Short Answer
By the end of this page, you will understand why strings.Join accepts a slice of strings rather than an array, how arrays and slices differ in Go, and how to correctly convert or declare your data so it can be joined. You will also see practical examples, common mistakes, and how this appears in real Go codebases.
Concept
In Go, strings.Join is designed to work with a slice of strings:
func Join(elems []string, sep string) string
That means its first argument must be of type []string.
In your code, however, reg is declared as:
reg := [...]string{"a", "b", "c"}
This creates an array, not a slice. In Go, arrays and slices are related, but they are different types.
[3]stringmeans: an array with exactly 3 strings[]stringmeans: a slice of strings of any length
Even though they look similar, Go does not automatically treat an array as a slice when calling a function that expects []string.
To use strings.Join, you have two common options:
- Declare the value as a slice from the start
- Slice the array using
reg[:]
This matters in real programming because many Go APIs use slices instead of arrays. Slices are more flexible, easier to pass around, and the standard library uses them heavily.
Mental Model
Think of an array as a fixed-size storage box with a label saying exactly how many compartments it has.
[3]string= a box with exactly 3 slots[5]string= a different box with 5 slots
A slice is more like a view or window into a sequence of items.
[]string= “some strings,” without fixing the count in the type
strings.Join says: “Give me a slice view of strings.”
If you hand it a fixed-size array box, Go says: “That is not the same type.”
When you write reg[:], you are saying: “Create a slice view over the whole array.” That gives strings.Join exactly what it expects.
Syntax and Examples
The correct syntax for strings.Join is:
strings.Join(sliceOfStrings, separator)
Example 1: Declare a slice directly
package main
import (
"fmt"
"strings"
)
func main() {
reg := []string{"a", "b", "c"}
result := strings.Join(reg, ",")
fmt.Println(result)
}
Output:
a,b,c
This is the simplest approach when you want to join values.
Example 2: Convert an array to a slice
package main
import (
"fmt"
"strings"
)
func main() {
reg := [...]string{"a", "b", "c"}
result := strings.Join(reg[:], ",")
fmt.Println(result)
}
Output:
a,b,c
Here, reg[:] creates a slice containing the entire array.
Example 3: Different separator
package main
import (
"fmt"
"strings"
)
func main() {
words := []string{"Go", "is", "fast"}
fmt.Println(strings.Join(words, " "))
}
Output:
Go is fast
Example 4: Empty slice
package main
import (
"fmt"
"strings"
)
func main() {
var items []string
fmt.Println(strings.Join(items, ","))
}
Output:
Joining an empty slice returns an empty string.
Step by Step Execution
Consider this example:
package main
import (
"fmt"
"strings"
)
func main() {
reg := [...]string{"a", "b", "c"}
result := strings.Join(reg[:], ",")
fmt.Println(result)
}
Step by step:
-
reg := [...]string{"a", "b", "c"}- Go creates an array of type
[3]string - It contains three values:
"a","b", and"c"
- Go creates an array of type
-
reg[:]- This creates a slice over the entire array
- The new value has type
[]string
-
strings.Join(reg[:], ",")strings.Joinreceives the slice- It places
","between each element - It builds the final string:
"a,b,c"
-
fmt.Println(result)- Prints:
a,b,c
Quick trace
| Expression | Type | Value |
|---|---|---|
reg | [3]string | {"a", "b", "c"} |
reg[:] | []string | {"a", "b", "c"} |
strings.Join(reg[:], ",") | string | "a,b,c" |
Real World Use Cases
Joining string slices is common in many kinds of Go programs.
Building CSV-like output
row := []string{"Alice", "25", "London"}
line := strings.Join(row, ",")
Creating log messages
parts := []string{"INFO", "server started", "port=8080"}
msg := strings.Join(parts, " | ")
Generating file paths or keys
segments := []string{"users", "42", "profile"}
path := strings.Join(segments, "/")
Formatting tags or labels
tags := []string{"go", "backend", "api"}
output := strings.Join(tags, ", ")
Combining SQL placeholders or dynamic content
When building text from known safe string parts, strings.Join is cleaner and more readable than repeated concatenation.
Real Codebase Usage
In real Go projects, developers usually prefer slices over arrays for data they want to pass to functions.
Common pattern: declare slices directly
headers := []string{"Content-Type", "Authorization", "X-Trace-ID"}
fmt.Println(strings.Join(headers, ", "))
This is common because slices work naturally with:
- function parameters
- loops
append- standard library functions
Pattern: join after collecting values
var errors []string
errors = append(errors, "name is required")
errors = append(errors, "email is invalid")
msg := strings.Join(errors, "; ")
This is often used in validation and error reporting.
Pattern: guard clause for empty data
func formatTags(tags []string) string {
if len(tags) == 0 {
return "no tags"
}
return strings.Join(tags, ", ")
}
Pattern: build parts first, then join
Instead of manually adding separators in a loop, developers often do this:
parts := []string{}
if true {
parts = append(parts, "name=alice")
}
if true {
parts = append(parts, "active=true")
}
query := strings.Join(parts, "&")
This avoids tricky separator logic like “add a comma except after the last item.”
Common Mistakes
1. Using an array instead of a slice
Broken code:
reg := [...]string{"a", "b", "c"}
fmt.Println(strings.Join(reg, ","))
Why it fails:
regis an arraystrings.Joinexpects[]string
Fix:
fmt.Println(strings.Join(reg[:], ","))
Or declare it as a slice:
reg := []string{"a", "b", "c"}
2. Confusing [3]string and []string
These are not interchangeable.
[3]stringand[4]stringare also different from each other[]stringis a separate type
3. Manually concatenating in a loop
Beginners often write:
result := ""
for i, s := range reg {
if i > 0 {
result += ","
}
result += s
}
This works, but strings.Join is simpler and clearer when you already have a slice of strings.
4. Expecting automatic conversion
Go does not automatically convert many types, even when the values look compatible.
Broken assumption:
// Go will not convert this automatically
arr := [...]string{"x", "y"}
_ = strings.Join(arr, "-")
Fix:
_ = strings.Join(arr[:], "-")
5. Forgetting that separators go between items
items := []string{"a", "b", "c"}
fmt.Println(strings.Join(items, ","))
Output:
a,b,c
The separator is placed between elements, not at the beginning or end.
Comparisons
| Concept | Type | Fixed size? | Common for function parameters? | Works with strings.Join directly? |
|---|---|---|---|---|
| Array | [3]string | Yes | No | No |
| Slice | []string | No | Yes | Yes |
Array vs slice in Go
arr := [...]string{"a", "b", "c"} // array
slc := []string{"a", "b", "c"} // slice
Use an array when:
- the fixed length is part of the data itself
- you explicitly want a value with a known size in its type
Use a slice when:
- you want to pass data to functions
- you want flexibility
- you need to use most standard library APIs
strings.Join vs manual concatenation
| Approach | Best for | Pros | Cons |
|---|---|---|---|
strings.Join | Joining existing []string values | Clear, idiomatic, simple | Requires a slice |
+ concatenation | A few string pieces | Easy for very small cases | Gets messy in loops |
| Manual loop | Custom formatting logic | Flexible | More code, easier to make mistakes |
Cheat Sheet
// strings.Join signature
strings.Join(elems []string, sep string) string
Use a slice directly
items := []string{"a", "b", "c"}
result := strings.Join(items, ",")
Convert an array to a slice
arr := [...]string{"a", "b", "c"}
result := strings.Join(arr[:], ",")
Key rules
strings.Joinneeds[]string[3]stringis an array, not a slicearr[:]creates a slice over the whole array- Empty slice joined with any separator returns
"" - Separator is inserted between elements only
Common forms
strings.Join(words, " ") // sentence
strings.Join(parts, "/") // path-like output
strings.Join(tags, ", ") // comma-separated list
strings.Join(lines, "\n") // multiline string
Quick fix for the original error
reg := [...]string{"a", "b", "c"}
fmt.Println(strings.Join(reg[:], ","))
Or better:
reg := []string{"a", "b", "c"}
fmt.Println(strings.Join(reg, ","))
FAQ
Why does strings.Join not accept an array in Go?
Because its function signature explicitly requires []string, which is a slice type. Arrays and slices are different types in Go.
How do I convert an array to a slice in Go?
Use slicing syntax:
arr[:]
This creates a slice covering the whole array.
Should I use arrays or slices for strings in Go?
In most everyday Go code, use slices. Arrays are less common for function arguments because their size is part of the type.
Is strings.Join better than looping manually?
Yes, when you already have a slice of strings. It is shorter, clearer, and idiomatic.
What happens if I join an empty slice?
You get an empty string:
strings.Join([]string{}, ",") // ""
Can I join values that are not strings?
Not directly with strings.Join. You must first convert them to strings.
Does strings.Join add a separator at the end?
No. The separator is only placed between elements.
Mini Project
Description
Create a small Go program that formats a list of command-line style labels into a single readable string. This demonstrates how slices are collected and joined using strings.Join, which is a common pattern in logging, reporting, and configuration output.
Goal
Build a Go program that joins a list of labels into a comma-separated string and handles the empty case cleanly.
Requirements
- Declare a slice of strings containing at least three labels.
- Use
strings.Jointo combine them into one string. - Print the final result.
- Add a small function that returns a fallback message when the slice is empty.
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.