Skip to Content
Go Realm v1 is released 🎉
TopicsString

Strings in Go

A string in Go is an immutable, read-only slice of bytes — UTF-8 encoded by default.

📖 Analogy: Think of a string as a sealed envelope. You can read what’s written, copy it, slice it — but you can’t erase and rewrite inside the same envelope. Every “change” creates a new one.


1. The Critical Distinction — byte vs rune 🔥

This is the #1 interview trap for Go strings.

s := "héllo" // 'é' is a 2-byte UTF-8 character fmt.Println(len(s)) // 6 — bytes, NOT characters! fmt.Println(utf8.RuneCountInString(s)) // 5 — actual characters (runes) fmt.Println(s[1]) // 195 — a raw byte, NOT 'é' fmt.Printf("%c\n", s[1]) // Ã — garbage for multi-byte chars
byterune
Typeuint8int32
RepresentsOne raw byteOne Unicode code point (character)
len(s)✅ counts bytes❌ wrong for non-ASCII
Index s[i]✅ gets a byte❌ breaks multi-byte chars
range s❌ by byte✅ by rune (use this!)

Rule: For pure ASCII strings, byte indexing is fine. For any user-facing text or Unicode, always think in runes.


2. Declaration

s := "Hello, Go" // inferred type string var s string // zero value = "" (empty string, not nil) s := `raw string` // raw/backtick literal — newlines and \ are literal

Strings in Go are never nil — zero value is "".


3. Core Operations

s := "Hello, Go!" // Length len(s) // 10 (bytes) utf8.RuneCountInString(s) // 10 (runes — same here, all ASCII) // Indexing — returns a byte s[0] // 72 (byte value of 'H') fmt.Printf("%c", s[0]) // H // Slicing — returns a string s[0:5] // "Hello" s[7:] // "Go!" // Concatenation s1 := "Hello, " + "Go!" // creates a new string // Comparison — lexicographic, works directly with ==, <, > "abc" == "abc" // true "abc" < "abd" // true

4. Iteration — byte vs rune

s := "héllo" // ❌ Byte iteration — breaks on multi-byte chars for i := 0; i < len(s); i++ { fmt.Printf("%c ", s[i]) // h à © l l o — corrupted! } // ✅ Rune iteration — correct for all Unicode for i, r := range s { fmt.Printf("[%d]=%c ", i, r) // [0]=h [1]=é [3]=l [4]=l [5]=o } // Note: index jumps 1→3 because 'é' is 2 bytes

5. Concatenation Performance

MethodWhen to useNotes
+ operator1–2 joinsCreates a new string each time
fmt.SprintfFormatting onlySlowest — uses reflection
strings.JoinJoining a sliceEfficient, single allocation
strings.BuilderLoop / many joinsFastest — zero-copy growth
// ❌ Loop with + — O(n²) allocations result := "" for _, w := range words { result += w + " " } // ✅ strings.Builder — O(n) single allocation var b strings.Builder for _, w := range words { b.WriteString(w) b.WriteByte(' ') } result := b.String() // ✅ strings.Join — cleanest for slice → string result := strings.Join(words, " ")

6. strings Package Cheat Sheet

import "strings"
FunctionExampleOutputNotes
Containsstrings.Contains("Golang", "Go")truesubstring check
HasPrefixstrings.HasPrefix("Go", "G")truestarts with
HasSuffixstrings.HasSuffix("Go", "o")trueends with
Countstrings.Count("cheese", "e")3non-overlapping count
Indexstrings.Index("Go", "o")1first index, -1 if not found
ToUpperstrings.ToUpper("go")"GO"
ToLowerstrings.ToLower("GO")"go"
Splitstrings.Split("a,b,c", ",")["a","b","c"]empty sep splits by char
Joinstrings.Join([]string{"a","b"}, "-")"a-b"
Replacestrings.Replace("foo", "o", "0", 1)"f0o"-1 replaces all
ReplaceAllstrings.ReplaceAll("foo", "o", "0")"f00"cleaner than Replace with -1
Trimstrings.Trim("!!Go!!", "!")"Go"trims leading+trailing cutset
TrimSpacestrings.TrimSpace(" hi ")"hi"whitespace only
TrimPrefixstrings.TrimPrefix("GoLang", "Go")"Lang"removes prefix if present
TrimSuffixstrings.TrimSuffix("GoLang", "Lang")"Go"removes suffix if present
Repeatstrings.Repeat("Go", 3)"GoGoGo"
ContainsAnystrings.ContainsAny("abc", "xyz")falseany char in set
Fieldsstrings.Fields(" a b c ")["a","b","c"]splits on any whitespace
EqualFoldstrings.EqualFold("Go", "go")truecase-insensitive compare

7. strconv — Type Conversion

import "strconv" // int → string s := strconv.Itoa(42) // "42" // string → int n, err := strconv.Atoi("42") // 42, nil n, err := strconv.Atoi("abc") // 0, error // string → float f, err := strconv.ParseFloat("3.14", 64) // bool → string / string → bool s := strconv.FormatBool(true) // "true" b, err := strconv.ParseBool("true")

⚠️ strconv.Atoi always returns (int, error) — always handle the error in production.


8. Common Patterns

Reverse a string (Unicode-safe)

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) }

Check palindrome

func isPalindrome(s string) bool { r := []rune(strings.ToLower(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 character frequency (CP pattern)

freq := make(map[rune]int) for _, r := range s { freq[r]++ }

Check if two strings are anagrams

func isAnagram(a, b string) bool { if len(a) != len(b) { return false } freq := make(map[rune]int) for _, r := range a { freq[r]++ } for _, r := range b { freq[r]-- if freq[r] < 0 { return false } } return true }

9. Common Mistakes ⚠️

MistakeProblemFix
len(s) for character countcounts bytes, not charsutf8.RuneCountInString(s)
s[i] on non-ASCIIreturns raw byte, not char[]rune(s)[i] or range
+ in a loopO(n²) allocationsstrings.Builder or strings.Join
fmt.Sprintf for simple concatslowest optionuse + or Builder
Modify string directlycompile error — strings are immutableconvert to []byte or []rune first
Byte-iterate Unicode stringcorrupts multi-byte charsuse for _, r := range s

10. Interview Cheat Sheet

Q: What is a string in Go internally?

An immutable, read-only slice of bytes ([]byte), UTF-8 encoded. It is not a slice — it has no capacity, only a pointer and a length.

Q: What does len(s) return?

The number of bytes, not characters. For "héllo", len returns 6 (é is 2 bytes), but there are 5 characters. Use utf8.RuneCountInString(s) for true character count.

Q: How do you safely iterate over a string character by character?

Use for i, r := range s — this yields rune values and handles multi-byte UTF-8 correctly. Never use for i := 0; i < len(s) for character-level work.

Q: Are strings mutable in Go?

No. Strings are immutable. To modify, convert to []byte (for ASCII) or []rune (for Unicode), modify, then convert back with string(...).

Q: What is the fastest way to build a string in a loop?

strings.Builder — it maintains an internal buffer and only allocates once at b.String(). Using + in a loop creates a new string on every iteration.

Q: Difference between string(65) and strconv.Itoa(65)?

string(65) produces "A" (rune 65 = ‘A’). strconv.Itoa(65) produces "65" (the decimal digits). A very common mistake.