The strings package is Go’s everyday toolbox for working with text. Anything you’d do with a string — search inside it, split it, change its case, join a list of them — lives here. This page is a quick reference to the functions you’ll reach for most, plus the one performance trick (strings.Builder) that interviewers love to ask about.

Import it like this:

import "strings"

At a glance

Need to…Use
Check if text contains somethingContains, HasPrefix, HasSuffix
Find where something isIndex, LastIndex, Count
Split text into piecesSplit, SplitN, Fields
Combine pieces back togetherJoin
Change caseToLower, ToUpper
Swap one thing for anotherReplace, ReplaceAll
Clean up whitespace or charactersTrimSpace, Trim, TrimPrefix, TrimSuffix
Build a string in a loop (fast)strings.Builder
Compare without caring about caseEqualFold

The rest of this page walks through each group with examples.

Searching and testing

Contains, HasPrefix, HasSuffix

The “does this string contain X?” family. All return bool.

strings.Contains("hello world", "world")     // true
strings.ContainsAny("hello", "xyz!l")        // true  (any one char matches)
strings.HasPrefix("main.go", "main")          // true
strings.HasSuffix("main.go", ".go")           // true

Index, LastIndex, Count

The “where is X?” family. Return an int (-1 if not found).

strings.Index("hello", "ll")          // 2
strings.LastIndex("banana", "a")      // 5
strings.Count("banana", "a")          // 3
strings.Count("banana", "")           // 7  (matches between every char)

EqualFold — case-insensitive equality

The Go way to do "Hello".equalsIgnoreCase("hello"):

strings.EqualFold("Hello", "hello")   // true
strings.EqualFold("Go", "go")         // true

Faster and more correct than ToLower(a) == ToLower(b) for Unicode.

Splitting and joining

Split and Fields

Split cuts text at a separator you choose. Fields cuts at any run of whitespace.

strings.Split("a,b,c", ",")           // ["a", "b", "c"]
strings.SplitN("a,b,c,d", ",", 2)     // ["a", "b,c,d"]  (max 2 pieces)

strings.Fields("  hello   world  ")   // ["hello", "world"]  (whitespace-aware)
strings.Fields("a\tb\nc")             // ["a", "b", "c"]

Use Fields when splitting on whitespace, not Split(s, " "). Split keeps empty strings around tabs and double-spaces. Fields does what you actually want.

Join — the reverse of Split

parts := []string{"a", "b", "c"}
strings.Join(parts, ", ")             // "a, b, c"
strings.Join(parts, "")               // "abc"

Transforming

Case

strings.ToLower("Hello World")        // "hello world"
strings.ToUpper("Hello World")        // "HELLO WORLD"

Replace and ReplaceAll

strings.Replace("banana", "a", "X", 2)   // "bXnXna"   (replace first 2)
strings.Replace("banana", "a", "X", -1)  // "bXnXnX"   (-1 means all)
strings.ReplaceAll("banana", "a", "X")   // "bXnXnX"   (same as -1, clearer)

Trimming

strings.TrimSpace("  hello  \n")          // "hello"
strings.Trim("***hello***", "*")          // "hello"
strings.TrimLeft("---hi", "-")            // "hi"
strings.TrimRight("hi---", "-")           // "hi"

strings.TrimPrefix("Mr. Smith", "Mr. ")   // "Smith"
strings.TrimSuffix("file.go", ".go")      // "file"

TrimPrefix is not the same as Trim. Trim("xxhello", "x") removes every x from the start. TrimPrefix("xxhello", "xx") removes the exact prefix "xx" once.

Repeat

strings.Repeat("ab", 3)               // "ababab"
strings.Repeat("-", 20)               // "--------------------"

Building strings the fast way

This is the most important section for interviews. Go strings are immutable — every time you do s = s + "x", Go creates a brand new string and copies all the old bytes plus the new ones. In a loop, this gets quadratically slow.

The slow way

s := ""
for i := 0; i < 10000; i++ {
    s += "x"   // creates a new string every iteration
}

The fast way — strings.Builder

strings.Builder writes to an internal buffer that grows as needed, so only the final .String() call copies the bytes once.

var b strings.Builder
for i := 0; i < 10000; i++ {
    b.WriteString("x")
}
result := b.String()

The full Builder API:

var b strings.Builder

b.WriteString("hello ")    // append a string
b.WriteRune('🎉')           // append a single rune
b.WriteByte('!')           // append a single byte

b.Len()                    // current length in bytes
b.Grow(1024)               // pre-allocate capacity (optional, but helpful)
b.Reset()                  // empty it and reuse

s := b.String()            // get the final string

Interview answer to “how would you build a big string?”: “Use strings.Builder — strings are immutable so += in a loop is O(n²); Builder amortizes to O(n).”

Reading strings character by character

Go strings are sequences of bytes, not characters. For ASCII text this doesn’t matter. For anything else (accents, emoji, non-English text), it does.

s := "héllo"
len(s)                              // 6  (bytes — é takes 2 bytes in UTF-8)
utf8.RuneCountInString(s)           // 5  (actual characters)

Byte iteration — fast but byte-level

for i := 0; i < len(s); i++ {
    fmt.Printf("%d=%c ", i, s[i])
}
// For "héllo" this prints garbled output for the é

Rune iteration — correct for any text

for i, r := range s {
    fmt.Printf("i=%d r=%c\n", i, r)
}

The i here is the byte index (so it can skip from 1 to 3 when crossing a 2-byte character), and r is the actual rune.

Interview gotcha: s[i] returns a byte (uint8), not a rune. If someone asks you to reverse a string, byte-reversing breaks UTF-8. Convert to []rune first:

func reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

Common interview patterns

Check if a string is a palindrome (case-insensitive)

func isPalindrome(s string) bool {
    s = strings.ToLower(s)
    r := []rune(s)
    for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
        if r[i] != r[j] {
            return false
        }
    }
    return true
}

Count words in a string

words := strings.Fields("hello   world go")
count := len(words)   // 3

Build a comma-separated list from numbers

nums := []int{1, 2, 3}
parts := make([]string, len(nums))
for i, n := range nums {
    parts[i] = strconv.Itoa(n)
}
result := strings.Join(parts, ", ")   // "1, 2, 3"

Check if a string starts with any of several prefixes

func hasAnyPrefix(s string, prefixes ...string) bool {
    for _, p := range prefixes {
        if strings.HasPrefix(s, p) {
            return true
        }
    }
    return false
}

Quick rules of thumb

  • Building in a loop?strings.Builder
  • Splitting by whitespace?Fields, not Split(s, " ")
  • Case-insensitive comparison?EqualFold, not lowercasing both sides
  • Removing an exact prefix/suffix?TrimPrefix / TrimSuffix, not Trim
  • Iterating over characters?for i, r := range s, not for i := 0; i < len(s); i++

Common mistakes

  • Using += to build big strings — quadratic time. Use Builder.
  • s[i] thinking it gives a character — it gives a byte. Use []rune(s) or range.
  • Split(s, " ") to break on whitespace — leaves empty strings around tabs/double-spaces. Use Fields.
  • ToLower(a) == ToLower(b) — works for ASCII, misses some Unicode edge cases. Use EqualFold.
Toggle theme (T)