Strings and Runes
A Go string is an immutable sequence of bytes — usually UTF-8 encoded text, but the language doesn't insist. Two consequences follow immediately.
First, immutability: you can read s[i] but never assign to it. "Changing" a
string always means building a new one. That makes strings safe to share and
cheap to pass around — like a read-only slice of bytes.
Second, len counts bytes, not characters:
package main
import "fmt"
func main() {
s := "héllo"
fmt.Println(len(s)) // 6, not 5 — é takes two bytes in UTF-8
fmt.Println(s[1], s[2]) // 195 169 — the raw bytes of é
}
Runes: characters, properly
Go calls a Unicode code point a rune (the type rune is an alias for
int32). The range loop is rune-aware — it decodes UTF-8 as it walks,
yielding the byte index where each rune starts and the rune itself:
for i, r := range "héllo" {
fmt.Printf("%d: %c\n", i, r)
}
// 0: h
// 1: é
// 3: l ← index jumps from 1 to 3: é used bytes 1 and 2
// 4: l
// 5: o
So: indexing a string gives bytes, ranging over it gives runes. When you need random access to characters, convert once:
runes := []rune("héllo")
fmt.Println(len(runes), string(runes[1])) // 5 é
[]rune(s) decodes the whole string into a slice of code points;
string(runes) encodes it back. This byte/rune split is deliberate design:
strings stay compact, simple bytes (no hidden re-encoding, O(1) len), and
the moments where Unicode decoding happens — range, []rune — are
visible in the code rather than ambient magic.
Byte-by-byte access is fine, though, whenever you're only looking for ASCII:
s[i] is a byte, and comparing it to 'a' (a character literal) works
because ASCII characters are single bytes in UTF-8.
The strings package
Everyday text work lives in the standard library's strings package:
import "strings"
strings.Contains("seafood", "foo") // true
strings.Split("a,b,c", ",") // []string{"a", "b", "c"}
strings.Join([]string{"a", "b"}, "-") // "a-b"
strings.ToUpper("gopher") // "GOPHER"
strings.ToLower("GOPHER") // "gopher"
Since strings are immutable, all of these return new strings — ToUpper
never touches its argument.
Building a string in a loop
That immutability has a cost you can trip over. Every s += part allocates
a whole new string and copies everything so far into it, so appending in a
loop does quadratic work: a thousand parts means a thousand allocations,
each larger than the last.
strings.Builder exists for this. It appends into one growable buffer and
hands you the finished string at the end, so the work is linear:
var b strings.Builder
for _, part := range parts {
b.WriteString(part)
}
result := b.String()
For two or three pieces, + is clearer and the difference is noise. Reach
for strings.Builder when the number of pieces is driven by data rather
than by you typing them out.
Your turn
The exercise asks you to count the vowels (a e i o u, case-insensitively,
ASCII only) in a string. Two clean routes: lowercase the whole string first
with strings.ToLower and compare against lowercase vowels, or check each
character against both cases. Either a byte loop or a range loop works for
ASCII — pick one and go.
Practice
1. Count the Vowels
BeginnerImplement
countVowelsso it returns how many vowels appear ins:func countVowels(s string) intVowels are
a,e,i,o,u— counted case-insensitively, so both"go"and"GO"contain one vowel. Only ASCII letters need to be handled; accented characters likeédon't count.For example,
countVowels("Go Gopher")returns3(o,o,e), andcountVowels("xyz")returns0.One tidy approach from the lesson: normalize once with
strings.ToLower(s), then loop over the result and compare each character against the five lowercase vowels (aswitchwithcase 'a', 'e', 'i', 'o', 'u':reads nicely). Checking both'a'and'A'per character works too.package main import "fmt" // countVowels returns the number of vowels (a, e, i, o, u) in s, // counted case-insensitively. ASCII only. func countVowels(s string) int { // TODO: implement — strings.ToLower may help return 0 } func main() { fmt.Println(countVowels("Go Gopher")) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.