Chapter 19: String
String data type.
In beginner tutorials (Tour of Go, W3Schools, freeCodeCamp, GeeksforGeeks, etc.) the string section usually gets quite a bit of attention because:
- almost every program deals with text
- strings in Go behave differently from many other languages
- many newcomers get surprised by immutability, UTF-8, byte vs rune, len() behavior, etc.
Let’s go through everything about strings in Go like we’re sitting together with VS Code open, typing and running examples live.
1. Core Facts About string in Go
| Property | Explanation | Example / Consequence |
|---|---|---|
| Type name | string | predeclared / basic type |
| Zero value | “” (empty string) | var s string → s == “” is true |
| Immutable | You cannot change a string after creation | s[0] = ‘x’ → compile error |
| UTF-8 encoded | Strings are read-only byte slices containing valid UTF-8 | Emoji takes 4 bytes, not 1 |
| len(s) | Returns number of bytes, not number of characters / runes | len(“హై”) = 6, not 2 |
| Comparable | == and != work (byte-by-byte) | “hello” == “hello” → true |
| Concatenation | Use + operator | “Hello” + ” ” + “World” |
| Backtick raw literals | multi-line — no escaping needed |
perfect for regex, HTML, JSON |
2. Declaration & Basic Examples (Run These!)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package main import "fmt" func main() { // Most common ways greeting := "Hello from Hyderabad! 🇮🇳" // short declaration → inferred string var name string = "Webliance" // explicit empty := "" // zero value // Raw string literal (backticks) — no escape characters needed multi := `Line 1 Line 2 with "quotes" C:\Users\Webliance\Documents` // Windows paths are easy fmt.Printf("greeting: %q (len = %d bytes)\n", greeting, len(greeting)) fmt.Printf("name: %q\n", name) fmt.Printf("empty: %q (zero value)\n", empty) fmt.Println("Raw:\n" + multi) } |
Typical output:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
greeting: "Hello from Hyderabad! 🇮🇳" (len = 30 bytes) name: "Webliance" empty: "" (zero value) Raw: Line 1 Line 2 with "quotes" C:\Users\Webliance\Documents |
Notice: the 🇮🇳 emoji takes 8 bytes (4 for 🇮 + 4 for 🇳), so “Hello from Hyderabad! 🇮🇳” = 22 ASCII bytes + 8 emoji bytes = 30 bytes.
3. Most Important Rule: Strings are Immutable
You cannot modify a string in place — any “change” creates a new string.
|
0 1 2 3 4 5 6 7 8 9 10 |
s := "hello" // s[0] = 'H' // compile error: cannot assign to s[0] (strings are immutable) s = "Hello" // ok — new string assigned to variable s s += " world!" // ok — creates new string "Hello world!" |
This is why string concatenation in a loop can be slow if done naively:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// BAD - quadratic time (many allocations) var result string for i := 0; i < 10000; i++ { result += "x" // creates new string each time } // Better - use strings.Builder (very common pattern) var sb strings.Builder for i := 0; i < 10000; i++ { sb.WriteByte('x') } result = sb.String() // one allocation at the end |
4. len() vs Number of Characters (Big Gotcha!)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
s1 := "Golang" // ASCII only s2 := "గోలాంగ్" // Telugu + English s3 := "Hello 🚀 World" // emoji fmt.Printf("%q → len = %d bytes, runes = %d\n", s1, len(s1), len([]rune(s1))) fmt.Printf("%q → len = %d bytes, runes = %d\n", s2, len(s2), len([]rune(s2))) fmt.Printf("%q → len = %d bytes, runes = %d\n", s3, len(s3), len([]rune(s3))) |
Output:
|
0 1 2 3 4 5 6 7 8 |
"Golang" → len = 6 bytes, runes = 6 "గోలాంగ్" → len = 30 bytes, runes = 7 "Hello 🚀 World" → len = 17 bytes, runes = 13 |
Rule of thumb:
- Want byte length (network, files, memory) → len(s)
- Want character / rune count → len([]rune(s)) or utf8.RuneCountInString(s)
5. Indexing & Slicing (Byte Level!)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
s := "Hello హై World 🚀" fmt.Println(s[0]) // 72 → byte 'H' (ASCII value) fmt.Println(s[:5]) // "Hello" (byte slice) fmt.Println(s[6:9]) // " హ" ← may cut in middle of multi-byte char → invalid UTF-8! // Safe way: work with []rune or utf8 package runes := []rune(s) fmt.Println(string(runes[6:9])) // "హై " |
Warning: slicing strings at arbitrary byte positions can produce invalid UTF-8 → avoid unless you know it’s safe (pure ASCII).
6. Common Real-World Patterns (2026 style)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// 1. Checking empty if name == "" { ... } // idiomatic // 2. Contains, HasPrefix, HasSuffix (strings package) import "strings" if strings.Contains(url, "youtube.com") { ... } // 3. To lower/upper/title lower := strings.ToLower("Hyderabad Rock!") upper := strings.ToUpper("telangana") // 4. Split / Join parts := strings.Split("apple,banana,cherry", ",") csv := strings.Join(parts, " | ") // 5. Builder for heavy concatenation var sb strings.Builder sb.WriteString("User: ") sb.WriteString(name) sb.WriteString(" from ") sb.WriteString(city) |
7. Your Quick Practice Exercise
Create string_practice.go:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package main import ( "fmt" "strings" "unicode/utf8" ) func main() { message := "Namaste Hyderabad! 🚀 తెలంగాణ" fmt.Printf("Bytes: %d Characters: %d\n", len(message), utf8.RuneCountInString(message)) // Extract first word safely firstSpace := strings.Index(message, " ") if firstSpace > 0 { fmt.Println("First word:", message[:firstSpace]) } // Uppercase city name fmt.Println("Upper:", strings.ToUpper("hyderabad")) // Contains check fmt.Println("Has rocket?:", strings.Contains(message, "🚀")) } |
Run it — change the string, add more Telugu/emoji/Hindi, see how len vs rune count behaves.
Questions now?
- Deep dive on strings.Builder vs + vs bytes.Buffer?
- UTF-8 decoding / encoding in detail?
- String ↔ []byte conversion safety?
- Or ready for rune type next? or composite types (slice/struct)?
Keep typing these examples — strings are everywhere in real Go code and understanding bytes vs runes early saves a lot of pain later. You’re doing fantastic! 💪🇮🇳 Let’s keep going! 🚀
