How to Split Strings in Go: Complete Guide with Examples
Executive Summary
Go’s standard library provides three primary methods for splitting strings: strings.Split(), strings.Fields(), and strings.FieldsFunc(). The choice between them depends on your delimiter and use case. strings.Split() is the most common, splitting on a fixed delimiter and returning a slice of substrings. Last verified: April 2026.
This intermediate-level guide covers the core approaches, performance considerations, edge case handling, and when to use each method. We’ll explore production-ready patterns that avoid common mistakes like ignoring empty strings, forgetting nil checks, and overlooking the performance implications of your chosen method. Most developers overlook that strings.Fields() automatically handles multiple consecutive whitespace characters—a feature that can save you from writing custom filtering logic.
Learn Go on Udemy
Main Data Table
| Method | Delimiter Type | Returns Empty Strings | Best Use Case | Time Complexity |
|---|---|---|---|---|
| strings.Split() | Single fixed string | Yes | CSV parsing, fixed delimiters | O(n) |
| strings.Fields() | Whitespace (any) | No | Word tokenization, log parsing | O(n) |
| strings.FieldsFunc() | Custom function | No | Complex delimiters, regex-like splits | O(n) |
| strings.SplitN() | Fixed string (limited) | Yes | Config parsing, limited splits | O(n) |
| regexp.Split() | Regex pattern | Yes | Complex pattern matching | O(n × m) |
Breakdown by Experience Level
Beginner: Start with strings.Split() for simple fixed-delimiter cases. This covers about 70% of real-world use cases.
Intermediate: Learn strings.Fields() for whitespace handling and strings.FieldsFunc() for custom logic. Understand the difference between keeping and discarding empty strings.
Advanced: Use regexp.Split() for pattern-based splitting, or implement custom buffering for streaming large data. Consider performance trade-offs with various approaches.
Core String Splitting Methods in Go
Let’s walk through the most practical approaches. Here’s strings.Split(), the workhorse method:
package main
import (
"fmt"
"strings"
)
func main() {
// Basic split on a fixed delimiter
csv := "apple,banana,cherry"
fruits := strings.Split(csv, ",")
fmt.Println(fruits) // [apple banana cherry]
// Handle trailing delimiters
withTrailing := "apple,banana,cherry,"
result := strings.Split(withTrailing, ",")
fmt.Println(result) // [apple banana cherry ]
fmt.Println(len(result)) // 4 — includes empty string
}
Notice that Split() preserves empty strings. If your CSV ends with a comma, you’ll get an empty element. This is by design—it gives you precise control.
For whitespace-based splitting, strings.Fields() shines:
package main
import (
"fmt"
"strings"
)
func main() {
// Fields automatically handles multiple spaces, tabs, newlines
text := "Go is a\tgreat\nlanguage"
words := strings.Fields(text)
fmt.Println(words) // [Go is a great language]
fmt.Println(len(words)) // 5
// Fields returns nil for empty input, not an empty slice
empty := ""
result := strings.Fields(empty)
fmt.Println(result == nil) // true
fmt.Println(len(result)) // 0
}
This is crucial: Fields() returns nil for empty input, while Split("") returns a slice with one empty string. Many bugs stem from not handling this distinction.
For custom splitting logic, use strings.FieldsFunc():
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
// Split on punctuation, not just whitespace
text := "Hello, world! How are you?"
words := strings.FieldsFunc(text, func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
})
fmt.Println(words) // [Hello world How are you]
// Another example: split on digits
code := "abc123def456ghi"
parts := strings.FieldsFunc(code, unicode.IsDigit)
fmt.Println(parts) // [abc def ghi]
}
The predicate function returns true for characters that should be treated as delimiters. This gives you regex-like power without regex overhead.
When you need to limit the number of splits, use strings.SplitN():
package main
import (
"fmt"
"strings"
)
func main() {
// Split only on the first two delimiters
line := "key=value=extra=data"
parts := strings.SplitN(line, "=", 3) // 3 = max 3 parts
fmt.Println(parts) // [key value extra=data]
// Useful for parsing config files
configLine := "server.timeout=30000ms"
kv := strings.SplitN(configLine, "=", 2)
fmt.Println(kv[0]) // server.timeout
fmt.Println(kv[1]) // 30000ms
}
Handling Edge Cases and Common Pitfalls
Empty delimiter: Don’t pass an empty string to Split(). This panics. Always validate input.
// This panics:
// strings.Split("hello", "")
// Safe version:
delimiter := ""
if delimiter == "" {
fmt.Println("Error: empty delimiter")
return
}
result := strings.Split(input, delimiter)
Nil or empty input: Both Split() and Fields() handle empty strings gracefully, but behave differently:
empty := ""
fmt.Println(strings.Split(empty, ",")) // [""] — one empty element
fmt.Println(strings.Fields(empty)) // [] or nil — empty
// Safe wrapper:
func SafeSplit(s, sep string) []string {
if s == "" {
return []string{}
}
return strings.Split(s, sep)
}
Trailing delimiters: If you need to strip empty trailing elements:
csv := "a,b,c,"
parts := strings.Split(csv, ",")
// Remove empty trailing elements
for len(parts) > 0 && parts[len(parts)-1] == "" {
parts = parts[:len(parts)-1]
}
fmt.Println(parts) // [a b c]
Comparison with Alternative Approaches
| Approach | Syntax Simplicity | Performance | Flexibility | Ideal For |
|---|---|---|---|---|
| strings.Split() | Very High | Fast (single pass) | Fixed delimiters only | CSV, simple tokenization |
| strings.Fields() | Very High | Fast | Whitespace only | Word splitting, log parsing |
| strings.FieldsFunc() | Medium | Fast (user function called per rune) | Very High | Custom delimiter rules |
| regexp.Split() | Low (regex compilation) | Slower (regex engine) | Very High | Pattern-based splitting |
| Manual loop (bufio.Scanner) | Low | Fastest (streaming) | Very High | Large files, streaming data |
Key Factors When Choosing a Method
1. Delimiter Type: Fixed strings call for Split(). Whitespace patterns? Use Fields(). Complex patterns demand FieldsFunc() or regex. Know your delimiter before writing code.
2. Empty String Behavior: Split() preserves empty strings; Fields() discards them. If you’re parsing CSV with optional fields, Split() is correct. For word tokenization, Fields() avoids post-filtering.
3. Performance at Scale: All standard library methods are O(n) and fast for typical strings (under 1MB). Avoid regexp.Split() in tight loops—compile the regex once if needed. For multi-gigabyte files, use bufio.Scanner to avoid loading everything into memory.
4. Error Handling: Go’s string split methods don’t return errors. Validate your input and delimiter upfront. The only runtime panic is passing an empty delimiter to Split().
5. Memory Efficiency: The returned slice points to substrings of the original string—no data is copied. Large slices of small strings are cheap. Modifying a substring after split doesn’t affect the original string; strings are immutable.
Historical Trends
Go’s string-splitting API has remained stable since Go 1.0 (2012). The core methods—Split(), Fields(), and FieldsFunc()—haven’t changed. In Go 1.18 (2022), generics were introduced, but string splitting didn’t need them since it already works on any string type. Recent versions (1.20+) added strings.Cut() for simple binary splits, reducing boilerplate for key-value parsing. No major changes are expected; the API is well-designed and performant.
Expert Tips
Tip 1: Use strings.Cut() for key-value pairs. If you’re splitting exactly once on a delimiter, Cut() is cleaner than SplitN():
line := "key=value"
key, value, ok := strings.Cut(line, "=")
if !ok {
fmt.Println("No delimiter found")
}
Tip 2: Pre-compile regex for repeated splits. If you use regexp.Split() in a loop, compile once:
pattern := regexp.MustCompile(`[,\s]+`)
for _, line := range lines {
parts := pattern.Split(line, -1)
// process parts
}
Tip 3: Trim before splitting to avoid empty leading/trailing elements. For robust CSV parsing, trim the string first:
csv := " apple, banana, cherry "
parts := strings.Split(strings.TrimSpace(csv), ",")
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
}
fmt.Println(parts) // [apple banana cherry]
Tip 4: Stream large files instead of splitting in memory. For files over 100MB, use bufio.Scanner:
file, err := os.Open("large.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, ",")
// process line
}
Tip 5: Benchmark your choice. If split performance matters, benchmark Split() vs Fields() vs FieldsFunc() with your actual data. You might be surprised—often the simpler method is fastest.
FAQ Section
Q: What’s the difference between strings.Split() and strings.Fields()?
A: Split() splits on a fixed delimiter string and preserves empty elements. Fields() splits on any whitespace (spaces, tabs, newlines) and discards empty elements. Example: strings.Split("a,,b", ",") returns ["a", "", "b"], while strings.Fields("a b") returns ["a", "b"]. Use Split() for CSV; use Fields() for word tokenization.
Q: Does splitting a string in Go create a copy of the data?
A: No. The returned slice contains pointers to substrings of the original string. Since Go strings are immutable, this is safe and efficient. The slice itself is newly allocated, but the string data is not copied. This means you can split a 1GB string with minimal overhead.
Q: How do I split a string by multiple delimiters?
A: Use strings.FieldsFunc() with a custom predicate, or use regexp.Split() with a pattern. For example, to split on both commas and semicolons:
parts := strings.FieldsFunc(text, func(r rune) bool {
return r == ',' || r == ';'
})
Or with regex: re := regexp.MustCompile(`[,;]`); parts := re.Split(text, -1)
Q: What happens if I pass an empty string as the delimiter to strings.Split()?
A: It panics. Go does not allow empty delimiters in Split(). Always validate your delimiter input. If you need to split into individual characters, use a range loop: for _, char := range str { /* process char */ }.
Q: How do I handle trailing empty strings from Split()?
A: Check the last element and trim if needed:
parts := strings.Split("a,b,c,", ",")
if len(parts) > 0 && parts[len(parts)-1] == "" {
parts = parts[:len(parts)-1]
}
Alternatively, trim the input first: strings.Split(strings.TrimSuffix(s, ","), ",")
Conclusion
Splitting strings in Go is straightforward with the right tool. For most cases, strings.Split() handles fixed delimiters, strings.Fields() handles whitespace, and strings.FieldsFunc() handles custom logic. Avoid common pitfalls: validate delimiters, handle empty input explicitly, and trim whitespace when parsing user-supplied data. For performance-critical code, benchmark your approach—the standard library is highly optimized, and you’ll rarely need to go custom. Use the official Go documentation as your primary reference, and always test with edge cases before deploying to production.
Learn Go on Udemy