Immutable character arrays: palindromes, anagrams, frequency counting, substring vs subsequence, and the classic pattern-matching algorithms.
0 of 7 lessons checked off
Introduction
What it is
A string is an immutable sequence of characters — effectively a read-only array. Every 'modification' in Python builds a new string.
Two definitions interviews constantly test: a substring is contiguous ('cde' in 'abcdef'); a subsequence keeps order but may skip ('ace' in 'abcdef').
Why it matters
String questions are a permanent fixture of interviews because they combine array technique with hashing (frequency maps), two pointers (palindromes), sliding windows (longest substring …), and DP (edit distance).
Immutability has real complexity consequences: s += ch in a loop is O(n²), the single most common performance bug in Python string code.
How it works
Index and slice like arrays: s[i] is O(1), s[a:b] copies O(b−a).
Count characters with a hash map (or Counter) to answer anagram/frequency questions in O(n).
Compare from both ends with two pointers for palindromes; build results in a list and ''.join once.
Where it's used
Search engines, DNA sequence analysis, spell checkers, log parsing, and every compiler lexer are string algorithms at industrial scale.
In interviews
Valid anagram / group anagrams (frequency signatures), valid palindrome (two pointers), longest substring without repeats (window), longest common prefix, and pattern matching (KMP / Rabin-Karp) as advanced follow-ups.
Analogy: A string is a word carved in stone: you can read any letter instantly, but 'changing' one means carving a whole new stone — so plan your edits and carve once.
Interactive diagram
Compare outermost characters and walk inward — the canonical O(n)/O(1) string technique.
r0L
a1
c2
e3
c4
a5
r6R
Is "racecar" a palindrome?
Compare the outermost characters and walk inward. Any mismatch answers 'no' immediately — no need to look at the rest.
1 / 5
Lessons in this topic
Check off lessons as you go — your progress is saved in this browser.
String traversal and immutability
Indexing, slicing costs, and why strings can't change in place.
15 min
Character frequency
Counter / dict signatures; the tool behind anagram questions.
15 min
Palindromes
Two-pointer check, ignoring non-alphanumerics, and expand-around-centre.
20 min
Anagrams
Sorted-string vs frequency-map signatures and their costs.
15 min
Substrings vs subsequences
Contiguous vs order-preserving; counts (n(n+1)/2 vs 2ⁿ) and which patterns apply.
15 min
Longest common prefix
Vertical scanning and why sorting first can shortcut.
15 min
Pattern searching: KMP, Rabin-Karp, Z (overview)
Beating O(n·m): failure functions, rolling hashes, Z-arrays.
35 min
Operations
Character frequency and anagram check
Two strings are anagrams exactly when their frequency signatures match — one dict, one pass each.
Character frequency and anagram check
1fromcollectionsimportCounter234defis_anagram(s:str,t:str)->bool:5"""True if t rearranges s. O(n) time, O(k) space (k = alphabet)."""6iflen(s)!=len(t):7returnFalse8returnCounter(s)==Counter(t)91011defgroup_anagrams(words:list[str])->list[list[str]]:12"""Group words sharing a frequency signature. O(total chars)."""13groups:dict[tuple[int,...],list[str]]={}14forwordinwords:15signature=[0]*2616forchinword:17signature[ord(ch)-ord("a")]+=118groups.setdefault(tuple(signature),[]).append(word)19returnlist(groups.values())
Time: O(n) per string — vs O(n log n) for the sort-based signatureSpace: O(k) for the alphabet-size counter
Edge cases
Length mismatch: answer immediately, no counting needed.
Unicode input makes the 26-slot trick wrong — fall back to Counter.
Empty strings are anagrams of each other.
Common mistakes
Sorting both strings (O(n log n)) and calling it optimal when counting is O(n).
Using a list as a dict key — lists aren't hashable; convert to tuple.
Valid palindrome (two pointers)
Walk from both ends toward the middle; any mismatch ends it. Filters (case, punctuation) happen on the fly.
r0L
a1
c2
e3
c4
a5
r6R
Is "racecar" a palindrome?
Compare the outermost characters and walk inward. Any mismatch answers 'no' immediately — no need to look at the rest.
Time: O(S) where S = sum of compared charactersSpace: O(1)
Edge cases
Empty list → empty prefix.
Any empty string in the list → empty prefix.
Identical words → the whole word.
Common mistakes
Comparing only the first and last words without sorting first (that shortcut requires sorting).
Index error by not checking col against each word's length.
KMP pattern search (failure function)
Precompute, for each pattern prefix, the longest proper prefix that is also a suffix — then never re-examine matched text.
KMP pattern search (failure function)
1defkmp_search(text:str,pattern:str)->int:2"""Indexoffirstoccurrenceofpatternintext,else-1.3O(n+m)—thetwo-pointerscannevermovesbackwardsintext."""4ifnotpattern:5return067# failure[i] = length of longest proper prefix of pattern[:i+1]8# that is also a suffix of it9failure=[0]*len(pattern)10k=011foriinrange(1,len(pattern)):12whilek>0andpattern[i]!=pattern[k]:13k=failure[k-1]14ifpattern[i]==pattern[k]:15k+=116failure[i]=k1718matched=0
Time: O(n + m) vs O(n·m) naiveSpace: O(m) for the failure table
Edge cases
Empty pattern matches at index 0.
Pattern longer than text → −1.
Repetitive patterns like 'aaaa' are exactly where naive matching degrades and KMP shines.
Common mistakes
Rebuilding the failure table with nested loops (making it O(m²)).
Resetting matched to 0 on mismatch instead of failure[matched−1] — that discards the whole point of KMP.
Complexity analysis
Operation
Best
Average
Worst
Space
Index s[i]
O(1)
O(1)
O(1)
—
Slice s[a:b]
O(b−a)
O(b−a)
O(n)
O(b−a)
Concat s + t
O(n+m)
O(n+m)
O(n+m)
O(n+m)
''.join(parts)
O(total)
O(total)
O(total)
O(total)
Anagram check (Counter)
O(n)
O(n)
O(n)
O(k)
KMP search
O(n+m)
O(n+m)
O(n+m)
O(m)
n, m = string lengths; k = alphabet size. The join row is why builders beat += loops.
Python implementation
Production-quality code with type hints, validation, and docstrings.
A StringBuilder: turning O(n²) concatenation into O(n)
1classStringBuilder:2"""Accumulatepartsinalist;materialiseoncewithbuild().3MirrorswhatJava's StringBuilder does and what ''.joinenables."""45def__init__(self)->None:6self._parts:list[str]=[]7self._length=089defappend(self,text:str)->"StringBuilder":10ifnotisinstance(text,str):11raiseTypeError("StringBuilder.append expects a str")12self._parts.append(text)13self._length+=len(text)14returnself# allow chaining1516def__len__(self)->int:17returnself._length18
What interviewers expect you to know
Definitions to state precisely
Substring: contiguous slice — there are n(n+1)/2 of them. Subsequence: order-preserving selection — there are 2ⁿ.
Anagram: same multiset of characters — equal frequency signatures.
Python strings are immutable: every edit allocates.
Frequent follow-ups
"What if it's Unicode?" — alphabet-size arrays (26 slots) stop working; switch to a dict/Counter and say so.
"Can you avoid the O(n) reversed copy?" — two pointers give O(1) space.
"How would you search many patterns in one text?" — that's Aho-Corasick / trie territory; naming it is enough at most levels.
How to explain string problems
Classify the question out loud: frequency (hash), symmetry (two pointers), contiguous constraint (window), alignment/edits (DP). The classification is most of the answer.
Mention the += trap unprompted when building output — interviewers at Python shops listen for it.
Common mistakes
String concatenation in a loop
result += ch copies everything so far on every iteration: O(n²). Append to a list and ''.join once.
Confusing substring with subsequence
Sliding window solves substring ('contiguous') problems; subsequence problems usually need DP or greedy — the word in the prompt decides the technique.
Forgetting normalisation
Case, spaces, and punctuation silently break palindrome/anagram checks. Ask what counts as a character before coding.
Reversing when O(1) space was requested
s[::-1] allocates a full copy. The two-pointer scan does the same job without it.
Assuming 26 lowercase letters
signature[ord(c) − 97] crashes on 'É'. State the assumption or use a dict.
Practice problems
Hand-picked from the 355-problem DSA Atlas. Reveal hints and solutions progressively; mark and bookmark as you solve.
5 questions across concepts, code output, complexity, and scenarios. Answer all, then submit for explanations.
Frequently asked questions
Why are Python strings immutable at all?
Immutability makes strings hashable (usable as dict keys), thread-safe, and safely shareable without defensive copies. The cost is copy-on-modify — hence the join idiom.
Do I need to memorise KMP for interviews?
For most companies, recognising when naive matching degrades and naming KMP/Rabin-Karp is enough; implementing the failure function from scratch is a senior/competitive-level ask. Understand it once so you can reason about it.
Counter vs sorting for anagrams — which should I lead with?
Counter: O(n) beats O(n log n), and it generalises to 'group anagrams' via frequency-tuple keys. Mention sorting as the simpler-but-slower alternative.
Summary & cheat sheet
Key takeaways
Strings are immutable arrays: reads O(1), edits allocate.
Frequency map = anagram tool; two pointers = palindrome tool; window = 'longest substring' tool.
Substring is contiguous, subsequence is not — the prompt's word choice picks your technique.
Build output in a list, join once.
Formulas & cheat sheet
Substrings of length-n string: n(n+1)/2 (+1 empty)
Subsequences: 2ⁿ
KMP: O(n + m) with failure[i] = longest proper prefix of p[:i+1] that is also its suffix
Interview checklist
I can write the two-pointer palindrome check with filters.
I can group anagrams with a frequency-tuple key.
I can say why += in a loop is O(n²) and what to do instead.
I can define substring vs subsequence without hesitation.