compare String Iteration with an alternative
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 Index family, all returning byte offsets (or -1):
strings.Index("hello", "l") // 2 — first "l"
strings.Index("hello", "lo") // 3 — substring works
strings.Index("hello", "z") // -1
strings.IndexRune("héllo", 'é') // 1 — é's byte offset
strings.IndexAny("hello", "oe") // 1 — first of any
strings.LastIndex("hello", "l") // 3 — from the rightExecuted. The -1 convention: every search returns -1 for absent (never an error, never a bool) — the caller's check is if idx >= 0 or idx != -1, and the classic split idiom is idx := strings.Index(s, ":"); if idx < 0 { not found } else { key, val := s[:idx], s[idx+1:] }.
The byte-offset design: results compose with slicing — found positions slice cleanly. The rune relationship: IndexRune gives the BYTE offset OF a rune — searching for 'é' returns 1 (where its 2 bytes start), not rune-index 1. Both units live together: search in bytes, count in runes when needed.
The contains-vs-index relationship: strings.Contains(s, sub) is Index(s, sub) >= 0 — the boolean wrapper, same machinery. Use Contains when you don't need WHERE; Index when you do.
compare String Iteration with an alternative
Direct Answer
strings.Index(s, substr) — byte offset of the first occurrence, -1 if absent. For a single rune: strings.IndexRune. For any of a set: IndexAny. All return byte offsets — compose directly with slicing.
Deep Dive & Architecture
The implementation tiering: Index uses an optimized search (the runtime's search handles the small-pattern cases with fast paths — the simple byte scan for 1-byte needles, a two-way/Rabin-Karp-flavored approach for longer ones). IndexRune delegates to Index(s, string(r)) for multibyte runes — or a single-byte scan for ASCII-width runes. IndexByte is the raw primitive the others bottom out in. The API surface is conveniences over three primitives — byte, rune, substring — and each exists because the caller's needle is naturally one of the three.
The composable idiom this family enables — the parser's bread and butter: Index + slice, chained. Find separator, split there; find the next, split again; the 'walk the string by found positions' pattern. strings.Cut (Go 1.18) formalized the single most common instance: Cut(s, sep) returns (before, after, found) — the Index-plus-slice-with-a-name that replaces the four-line idiom. Before Cut, every parser hand-wrote it; the stdlib watched the pattern and promoted it — API evolution in miniature.
The case-insensitive and pattern boundaries: EqualFold for case-insensitive equality (no case-insensitive Index in the stdlib — fold both sides, or use a regexp with (?i)); regexp for pattern positions (submatch indices — the general tool at ~10-50x the cost). The tiers, restated: bytes/rune/substring (Index family — fast, literal), case-folded (manual or strings.EqualFold for equality only), pattern (regexp — general, slow). Choosing by needle-expressiveness is the discipline.
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?
- What is a common mistake when using String Iteration?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.