GoGo example

How to split a string in Go

7 min read▶ Runs in an isolated hosted runtimeUpdated Aug 2026

Quick answer

Use strings.Split(s, sep), which returns a []string of every piece between the separators: strings.Split("go,rust,zig", ",") gives [go rust zig]. For whitespace use strings.Fields(s) instead — it collapses runs of spaces and tabs and throws away the empty pieces that Split(s, " ") would leave behind.

Splitting is one of those tasks where the wrong function almost works, and the bug shows up weeks later on a line with two spaces in it. Go's strings package gives you half a dozen splitters plus bufio.Scanner, and each one has an opinion about empty fields: Split keeps every one, Fields drops them all, Cut refuses to make more than two pieces. This page walks the lot, shows the empty-string and separator-not-found cases with real output, and finishes with a table so you can pick in ten seconds. Each example runs on this page — hit Run, then edit the code and run it again.

1strings.SplitRecommended

strings.Split(s, sep) cuts s at every occurrence of the sep string and returns the pieces. The separator is a string, not a set of characters and not a pattern, so " -> " is one separator rather than four. Nothing is trimmed and nothing is skipped: n separators always produce n+1 elements.

split.go

Output

Prints [go rust zig] 3, go zig, [a b c], then the two edge cases everybody trips over: a missing separator gives ["nope"] 1, and an empty input gives [""] 1 — length one, not zero, so len(parts) == 0 is never the test for "nothing here". One more thing worth knowing: the returned pieces are sub-slices of the original string, so no bytes are copied, but keeping one short field alive keeps the entire original string in memory.

2Splitting on whitespace: strings.Fields

Splitting a sentence into words with strings.Split(s, " ") is the single most common splitting bug in Go. Every double space produces an empty element, a tab isn't a separator at all, and leading or trailing spaces add empties at the ends. strings.Fields is the function you actually want: it splits around each run of whitespace, as defined by unicode.IsSpace, and returns only the non-empty pieces.

fields.go

Output

Fields gives the four words you expected — ["ship" "it" "fast" "now"] — while Split(s, " ") returns nine elements, five of them empty, with the tab and the newline glued into "fast\tnow\n". Note also that all-whitespace input gives [] 0 here, whereas Split would give a one-element slice. If you only need to strip the ends, don't split at all — strings.TrimSpace does that job.

3Limiting the pieces: SplitN and SplitAfter

strings.SplitN(s, sep, n) stops after n pieces and leaves the rest of the string — separators and all — in the final element. That is exactly what you want for values that may legally contain the separator, like a key=value pair whose value has an = in it. strings.SplitAfter is the other variant: same cuts, but each piece keeps its trailing separator.

splitn.go

Output

n = 2 gives ["usr" "local/go/bin"]; asking for 99 pieces of a four-piece string just gives the four; n = -1 means unlimited (strings.Split is literally SplitN(s, sep, -1)); and n = 0 returns nil, which prints true here — a real trap if n is computed. SplitAfter prints ["usr/" "local/" "go/" "bin"], keeping the slashes so the pieces still concatenate back to the original.

4Splitting exactly once: strings.Cut

Go 1.18 added strings.Cut(s, sep), and it has quietly replaced most two-piece splits. It returns before, after, found — three values instead of a slice you then have to length-check and index. Nothing is allocated: before and after are windows onto the original string. Reach for it any time you are parsing key=value, host:port or name<email>.

cut.go

Output

The first line prints HOST | db.internal:5432 | true — note the colon inside the value survives, because Cut only ever cuts once. When the separator is absent you get "PLAIN" "" false: the whole input comes back as before, which is why the found flag matters — an empty after is ambiguous on its own, as EMPTY= in the loop shows. Go 1.20 added the same shape for affixes: strings.CutPrefix and strings.CutSuffix return the trimmed string plus a found bool.

5Splitting a string into lines

strings.Split(text, "\n") looks like the answer and is wrong twice: a file that ends in a newline gains a phantom empty last line, and Windows text keeps a stray \r on the end of every line — which you then compare against and lose an afternoon to. bufio.Scanner over a strings.NewReader handles both, and it's the same code you would use for a real file.

lines.go

Output

The naive split prints ["alpha" "beta\r" "gamma" ""] 4 — four "lines" for three lines of text, one of them carrying a carriage return. The scanner prints alpha, beta, gamma and lines: 3. It does keep genuinely blank lines, which is usually what you want. Two caveats: check sc.Err() after the loop, and remember the scanner rejects any line longer than bufio.MaxScanTokenSize (64 KB) unless you hand it a bigger buffer with sc.Buffer(...).

6Several delimiters at once — and single characters

Split takes one separator string, so "split on ; or , or |" needs a different tool. strings.FieldsFunc takes a predicate and cuts at every run of runes that satisfies it — no regexp engine, no allocation beyond the result. regexp.Split is the heavier option, worth it when the pattern is data or genuinely needs regex. The two disagree about empty fields, which is the detail that decides most of these bugs.

multi.go

Output

Both splitters give ["go" "rust" "zig" "c"] on the mixed input, but on "a,,b" FieldsFunc returns ["a" "b"] and regexp.Split returns ["a" "" "b"] — pick the one whose empty-field policy you want. Splitting on "" is UTF-8 aware, so "Go言" becomes three pieces even though the last line prints 5 3: five bytes, three characters. For characters prefer []rune(s) or for _, r := range s — they give you rune values instead of a slice of one-character strings. (The same byte-versus-rune split decides reversing a string.)

7Which should you use?

MethodEmpty fieldsAllocationsBest for
strings.SplitKeptOne sliceOne known separator — CSV-ish data
strings.FieldsDroppedOne sliceWords separated by whitespace
strings.SplitNKept, up to nOne sliceStopping after n pieces
strings.CutN/A — two piecesNonekey=value, host:port
strings.FieldsFuncDroppedOne sliceAny set of separator characters
regexp.SplitKeptSlice + regexp enginePatterns, not fixed strings
bufio.ScannerBlank lines keptOne reused bufferLines, huge or streamed input

Frequently asked questions

How do I split a string by a comma in Go?

Call strings.Split(s, ","), which returns a []string of the pieces — strings.Split("go,rust,zig", ",") gives [go rust zig]. If the fields may have spaces around them, run each one through strings.TrimSpace. For real CSV with quoting and embedded commas, use encoding/csv rather than splitting by hand.

Why does strings.Split give me empty strings in the result?

Because Split never skips anything: n separators always produce n+1 pieces. Two separators in a row give an empty piece between them, a leading or trailing separator gives an empty piece at that end, and splitting the empty string "" returns a one-element slice containing "" — so len(parts) == 0 is never true. If you want the empties gone, use strings.Fields for whitespace or strings.FieldsFunc for other separators.

What is the difference between strings.Split and strings.Fields?

Split takes an explicit separator string and keeps every empty field; Fields takes no separator, splits around each run of whitespace (as defined by unicode.IsSpace, so spaces, tabs and newlines all count) and drops empty fields. On " ship it fast", Fields returns three words while Split(s, " ") returns nine elements, five of them empty.

How do I split a string on multiple delimiters in Go?

Use strings.FieldsFunc(s, func(r rune) bool { ... }) and return true for every rune that should act as a separator — it handles any set of characters without a regexp and discards empty fields. If the delimiters are a pattern or come from configuration, compile a regexp and call re.Split(s, -1); that keeps empty fields, unlike FieldsFunc. Another option is strings.NewReplacer to normalise every delimiter to one character, then a plain strings.Split.

How do I split a Go string into lines?

Prefer bufio.NewScanner(strings.NewReader(text)) and loop on sc.Scan(): it strips both \n and \r\n, and it does not produce a phantom empty line for text ending in a newline. strings.Split(text, "\n") does both of those wrong — check sc.Err() after the loop, and raise the 64 KB line limit with sc.Buffer(...) if your lines can be longer.

How do I split a Go string into individual characters?

Use []rune(s) to get a slice of code points, or for i, r := range s to walk them without allocating — both decode UTF-8, so a multi-byte character stays whole. strings.Split(s, "") also works and is UTF-8 aware, but it hands you a []string of one-character strings, which is usually more allocation than you need. Indexing s[i] gives a byte, not a character.

Can I split a string without allocating a slice?

Yes. Go 1.24 added iterator versions — strings.SplitSeq, strings.SplitAfterSeq, strings.FieldsSeq, strings.FieldsFuncSeq and strings.Lines — which yield one piece at a time to a for range loop instead of building a []string. Before 1.24, the usual trick is a loop over strings.Cut, taking one field per iteration; strings.Cut itself allocates nothing because it returns windows onto the original string.