compare String Immutability with an alternative
Fast recall
Quick revision
Core answer
Practical understanding of String Immutability.
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
Slicing a string is O(1) and shares memory — the substring is a new (pointer, length) header pointing INTO the original's bytes:
s := "hello world"
sub := s[0:5] // "hello" — no copy, shares s's bytesThe sharing is invisible to correct code (strings are immutable — neither can change the bytes), but it has a lifetime consequence: the substring's pointer anchors the original's memory. A tiny 5-byte substring of a 10MB parsed document keeps all 10MB alive as long as the substring exists.
That's the bug shape in long-running services: parse a huge payload, save a small token from it, drop the payload — but the token silently holds the whole thing. The fix is strings.Clone (Go 1.18+), which copies just the substring's bytes into a fresh allocation:
token := strings.Clone(s[start:end]) // fresh small string, original collectableThe related facts: slicing works on BYTES (can slice mid-rune — producing invalid UTF-8 if you're not careful with boundaries); the slice bounds are byte indices; and the same sharing applies to []byte slices (the append-gotcha family from the slices topic — but for strings, no append exists to fork them apart).
compare String Immutability with an alternative
Direct Answer
s[1:3] does NOT copy — the substring shares the original's underlying bytes (O(1), just a new pointer+len). Both strings stay alive together; use strings.Clone to break the anchor.
Deep Dive & Architecture
The memory layout, concretely: s := "hello world" compiles to (ptr→[104 101 108 108 111 32 119 ...], len 11). sub := s[0:5] compiles to (ptr→SAME bytes, len 5) — one pointer addition at slice time (ptr advances by the start index), zero bytes moved. That's the entire O(1).
The GC consequence — the mechanism of the anchor: Go's garbage collector traces reachability. While sub is reachable, its pointer makes the 11-byte allocation reachable — the whole allocation, because sub's pointer aims into its interior. The GC has no concept of 'only the first 5 bytes matter' — liveness is per-allocation. So the 10MB-payload case is literal: one small live string pins one large dead-except-for-it allocation.
Why strings.Clone was added (Go 1.18): the anchor pattern was a known memory-leak class in servers — strings.Fields/config parsers/logging pipelines all produce small strings carved from large inputs. Before Clone, the fixes were manual copies (string([]byte(sub)) — an idiom nobody understood was a memory fix); Clone names the intent and is the canonical answer. For []byte, append([]byte(nil), sub...) is the equivalent.
The correctness boundary alongside the lifetime one: s[a:b] slices bytes, so rune-aware code needs offsets from range iteration (the strings topic's core) — a substring cut mid-rune yields invalid UTF-8 that later decodes to U+FFFD. Two independent reasons to be deliberate with string slicing: memory (anchor) and encoding (rune boundaries).
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 Immutability, and when would you use it?
- When would you use String Immutability in a real project?
- What is a common mistake when using String Immutability?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.