a common mistake when using String Iteration
Fast recall
Quick revision
Core answer
Practical understanding of String Iteration.
Repeating a definition without saying when to use it.
Give one trade-off and say how you would test it.
Interview-ready guide
Interview answer
The classic loop is byte iteration — correct for its actual unit, wrong when read as characters:
s := "héllo"
for i := 0; i < len(s); i++ {
fmt.Println(s[i]) // 104, 195, 169, 108, 108, 111
}
// s[1] == 'é' — false: s[1] is 195 (a byte), 'é' is 233 (a rune)The bug shape: the loop LOOKS right on ASCII test data (every byte is a character), ships, and produces silently wrong behavior on the first international input — comparisons never match, counts are inflated, output corrupts when bytes are reassembled.
The correct forms, by need:
for i, r := range s { ... } // runes, free
r, size := utf8.DecodeRuneInString(s[i:]) // manual walk — r, its widthThe DecodeRuneInString pattern is the bridge when you need the classic loop's control (explicit bounds, early exit with position) but rune semantics — it returns (rune, byte-width), you advance i by size yourself. It's how the stdlib's own string functions are written.
The interview diagnosis: any classic loop over a string doing comparisons against character literals, or accumulating bytes into output, is suspect on sight — the question is 'what happens on é?' and the answer for s[i]-based code is 'corruption.'
a common mistake when using String Iteration
Direct Answer
s[i] is a BYTE — for multi-byte runes, the loop yields encoding fragments (195, 169 for é), not characters. Comparing s[i] to a rune literal never matches. Use range for characters, or utf8.DecodeRuneInString for manual walking.
Deep Dive & Architecture
Why the byte fragment is 195 and 169 specifically: é (U+00E9, decimal 233) encodes as two UTF-8 bytes — 0xC3 0xA9 (195, 169). The first byte (110xxxxx) marks a 2-byte sequence; the second (10xxxxxx) is the continuation. A byte loop stops at each — and s[1] (195) compares against 'é' (the rune constant 233) as uint8-vs-int32... never equal (even ignoring width, 195 ≠ 233 — the byte ISN'T the code point).
The silent-corruption path, end to end: a loop building output via s[i] appends the same bytes — so echoing input 'works' (bytes preserved). The corruption appears at COMPARISON and CHARACTER logic: counting vowels (each é counts 0 — no byte equals 'e' or 'é'), validating ('é' rejected as non-alpha — 195 and 169 both fail the test), or indexing by character count (position 2 is mid-rune). The bugs are logic bugs, not data loss — which makes them intermittent and test-resistant (ASCII tests pass).
The stdlib's own bridge functions, worth naming as the expert answer: utf8.DecodeRuneInString(s[i:]) — next rune + width from position i (the manual-walk primitive); strings.Map — rune-wise transform without explicit indices (the function gets each rune; positions are handled); utf8.RuneCountInString — the count when the loop's only job was length. Every one of these replaces a byte-loop idiom with the rune-correct equivalent — and code review treats the classic-loop-over-string-with-comparisons pattern as a finding, not a style choice.
Common Mistakes & Pitfalls
Repeating a definition without saying when to use it.
Check your understanding
Answer these without looking back at the article.
- What is String Iteration, and when would you use it?
- When would you use String Iteration in a real project?
- How would you compare String Iteration with an alternative?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.