A Go string is an immutable sequence of bytes, normally UTF-8 text. The operators work on the string itself (+, ==, <, indexing, slicing), and everything else lives in the standard strings package.
Splitting Strings
| Function | Splits on | Notes |
|---|---|---|
Split(s, sep) | every sep | keeps empty parts; Split("", ",") returns [""], one empty string |
SplitN(s, sep, n) | at most n parts | the last part holds the rest |
SplitAfter(s, sep) | every sep | keeps the separator on each part |
Fields(s) | runs of whitespace | never returns empty parts |
FieldsFunc(s, f) | characters where f is true | custom separators |
Cut(s, sep) | the first sep only | returns before, after, found (Go 1.18) |
The Split("", ",") case catches people: an empty input gives a slice of length 1, not 0. When splitting user input, Fields or a check for s == "" first avoids a phantom empty element.
strings.Cut is the cleanest way to split key=value, user@host and similar pairs. Go 1.24 also added iterator versions, strings.SplitSeq and strings.FieldsSeq, which yield parts one at a time without building a slice.
Searching
| Function | Returns |
|---|---|
Contains(s, sub) | true if sub appears in s |
ContainsAny(s, chars) | true if any character of chars appears |
HasPrefix(s, p) / HasSuffix(s, p) | true if s starts or ends with p |
Index(s, sub) | byte position of the first match, or -1 |
LastIndex(s, sub) | byte position of the last match, or -1 |
Count(s, sub) | number of non-overlapping matches |
EqualFold(a, b) | case-insensitive equality |
Every search is case-sensitive except EqualFold. To search case-insensitively, lowercase both sides or use EqualFold for whole-string comparisons.
Transforming
One trap: Trim, TrimLeft and TrimRight take a set of characters, not a prefix. strings.TrimLeft("abcab", "ab") removes every leading a and b and returns "cab". To remove an exact prefix or suffix, use TrimPrefix and TrimSuffix.
Concatenation and strings.Builder
+ joins strings, and fmt.Sprintf formats values into one. Both allocate a new string each time, because strings are immutable. That is fine for a handful of operations and slow in a loop, where each += copies everything built so far. Use strings.Builder instead:
A zero strings.Builder is ready to use. It implements io.Writer, so fmt.Fprintf writes into it directly. Call b.Grow(n) first if you know roughly how big the result will be. Do not copy a Builder after writing to it; pass a pointer.
Multiline and Raw Strings
Go has two kinds of string literal:
Double-quoted strings process escapes (\n, \t, \", \\, \u00e9) and must fit on one line. Backquoted raw strings take everything literally, including newlines, which makes them the way to write multiline strings, regular expressions, JSON and Windows paths. A raw string cannot contain a backquote, and carriage returns inside it are dropped. Note that the query above starts with a newline, since the literal begins right after the opening backquote.
Length, Indexing and Substrings
len(s) is the number of bytes. s[i] is a byte, and s[i:j] is a substring by byte positions:
ï and é take two bytes each, so the string is 12 bytes and 10 characters. Slicing by bytes is safe as long as the positions come from strings.Index or similar, which return byte offsets on character boundaries. To take "the first n characters", convert to []rune first. Runes and bytes explains UTF-8 in depth.
Strings Are Immutable
You cannot change a byte in place: s[0] = 'H' is a compile error (cannot assign to s[0] (neither addressable nor a map index expression)). Every function in strings returns a new string. To edit, convert to []byte or []rune, change the slice, and convert back:
b := []byte("hello")
b[0] = 'H'
s := string(b) // "Hello"
Immutability is also why substrings are cheap: s[2:5] shares memory with s instead of copying it. The flip side is that a small substring of a huge string keeps the whole huge string alive. Use strings.Clone (Go 1.20) when you keep a short piece of a large input.
Comparing Strings
== and != compare content, and <, > compare byte by byte, which is alphabetical for ASCII. strings.Compare exists but is rarely needed; slices.Sort on a []string uses <. For locale-aware sorting of non-English text, use golang.org/x/text/collate.
Converting Other Types
Numbers become strings with strconv.Itoa, strconv.FormatFloat or fmt.Sprintf, and strings become numbers with strconv.Atoi and strconv.ParseFloat. Do not write string(42): it produces the character with code point 42, "*". The type conversion page has the full table.
Frequently Asked Questions
How do I split a string in Go?
Use strings.Split(s, sep): strings.Split("a,b,c", ",") returns ["a" "b" "c"]. To split on any run of whitespace and drop empty parts, use strings.Fields(s). strings.SplitN limits the number of parts, and strings.Cut splits once around the first separator.
How do I write a multiline string in Go?
Use a raw string literal in backquotes:
query := `SELECT id, name
FROM users
WHERE active = true`
Everything between the backquotes is taken literally, including newlines and backslashes. A raw string cannot contain a backquote.
How do I check if a string contains a substring in Go?
strings.Contains(s, substr) returns a bool. Related functions: strings.HasPrefix, strings.HasSuffix, strings.Index (position or -1), strings.ContainsAny (any of a set of characters), and strings.EqualFold for case-insensitive equality.
What is the fastest way to concatenate strings in Go?
For a few strings, + is fine. When building a string in a loop, use strings.Builder: call WriteString repeatedly and String() once at the end. Repeated += in a loop copies the whole string each time, so it gets slow as the string grows. To join a slice with a separator, use strings.Join.
Why does len return the wrong length for my string?
len(s) returns the number of bytes, not characters. Go strings are UTF-8, where characters outside ASCII take 2 to 4 bytes, so len("héllo") is 6. Use utf8.RuneCountInString(s) for the number of Unicode code points.