how to split string in Go - Photo by Ferenc Almasi on Unsplash

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


View 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


View on Udemy →



Similar Posts