Foundation · Module 3 · Lesson 4
String fundamentals
Characters, immutability, building, and comparison.
Lesson 16 of 123
1d streakWhat is a String & The Immutability Superpower
At its core, a String is a sequential collection of characters stored together to represent text. While conceptually an array of characters, modern languages (Java, Python, JavaScript, C#) treat Strings as immutable objects—meaning once a string is allocated in memory, its characters can never be modified in-place.
JVM HEAP MEMORY
+-------------------------------------------------------------+
| |
| String s1 = "cat"; |
| String s2 = "cat"; |
| |
| +-----------------------------------------------------+ |
| | STRING CONSTANT POOL | |
| | | |
| | [ Address: 0x100 | Value: "cat" ] | |
| | ^ ^ | |
| | | | | |
| | s1 points here s2 reuses same memory! | |
| +-----------------------------------------------------+ |
| |
| String s3 = new String("cat"); |
| [ Address: 0x240 | Value: "cat" (Separate Heap Object) ] |
| ^ |
| s3 points here |
+-------------------------------------------------------------+Memory Deduplication (String Pool)
Identical string literals share a single object in the pool. If your app handles 100,000 users with city "New York", only one copy of "New York" exists in memory.
Security & Integrity
Strings store database passwords, file paths, and network socket URLs. If strings were mutable, another thread could modify a verified path after security checks pass (TOCTOU vulnerability).
Thread-Safety Out of the Box
Because strings cannot be modified, multiple concurrent threads can read, slice, and share them simultaneously with zero synchronization locks.
Cached HashCode for Fast Lookups
Because a string cannot change, its hashCode is calculated once and cached. This makes Strings the ideal, ultra-fast key for HashMaps and HashSets.
Architectural Boundary · Do Not Confuse
String Literal: s = "hello"
New Object: s = new String("hello")
String literals check and reuse instances from the String Constant Pool. Calling new String() forces the JVM to allocate a redundant duplicate object on the open heap.
The O(n²) Concatenation Trap vs StringBuilder
The single most common performance bug in coding interviews is repeatedly concatenating characters or strings with the + operator inside a loop. Because strings are immutable, every + operation copies the entire existing string to construct a new one.
Naive Concatenation in a Loop:
Iteration 1: "" + 'a' -> Allocates new String "a" (copies 1 char)
Iteration 2: "a" + 'b' -> Allocates new String "ab" (copies 2 chars)
Iteration 3: "ab" + 'c' -> Allocates new String "abc" (copies 3 chars)
Iteration 4: "abc" + 'd' -> Allocates new String "abcd" (copies 4 chars)
...
Total Characters Copied: 1 + 2 + 3 + ... + n = O(n²) Time & massive GC pressure!
Optimal StringBuilder Approach:
Internal Buffer: [ 'a' | 'b' | 'c' | 'd' | _ | _ | _ | _ ] (Capacity: 16)
Appends write directly into the next free index in O(1) amortized time!
Total Time for n appends: O(n)// SLOW: O(n^2) - Creates n intermediate string objects in memory!
String result = "";
for (int i = 0; i < n; i++) {
result += s.charAt(i); // Allocates a brand new copy every iteration
}
// FAST: O(n) - Uses an expandable backing array with amortized O(1) appends
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; i++) {
sb.append(s.charAt(i)); // Appends in-place
}
String finalResult = sb.toString();Interview Trap: String Comparison with == vs .equals()
==. The == operator compares memory addresses (object references), not characters. Always use str1.equals(str2) in Java to compare character contents.Core String Operations & Complexity Cheat Sheet
| Operation | Java / TS API | Time Complexity | Explanation |
|---|---|---|---|
| Character Access | s.charAt(i) / s[i] | O(1) | Direct array index lookup on internal buffer |
| Length Lookup | s.length() / s.length | O(1) | Length is stored as a cached integer field |
| Substring | s.substring(i, j) / s.slice(i, j) | O(k) | Copies k = (j - i) characters into a new string |
| String Equality | s1.equals(s2) / s1 === s2 | O(n) | Compares lengths first, then scans characters up to length n |
| Character Array | s.toCharArray() / s.split("") | O(n) | Allocates and copies n characters |
| Loop Concatenation | s += ch (inside loop) | O(n²) | Copies growing prefixes repeatedly |
| Builder Append | sb.append(ch) | O(1) amortized | Direct insertion into backing buffer; resizes geometrically |
| Sub-string Search | s.indexOf(sub) / s.includes() | O(n × m) | Scans text of length n for pattern of length m |
Active Recall Checkpoint
Given String a = "hello"; String b = new String("hello");, what does a == b evaluate to, and what does a.equals(b) evaluate to?
Hint: Think about where string literals vs new objects live in JVM memory.
Show answer & explanation
a == b is FALSE because a references the pooled literal while b references a distinct object on the heap. a.equals(b) is TRUE because both objects contain identical characters.
Essential Interview String Patterns
Most string questions in technical interviews fall into one of three core algorithmic patterns: Two Pointers, Frequency Counting, or Sliding Window.
// PATTERN 1: Two Pointers (Inward Convergence for Palindromes) - O(n) time, O(1) space
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}
// PATTERN 2: Frequency Array (Anagram Verification) - O(n) time, O(1) space
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
int[] counts = new int[26]; // Fixed size 26 for lowercase English letters
for (int i = 0; i < s.length(); i++) {
counts[s.charAt(i) - 'a']++;
counts[t.charAt(i) - 'a']--;
}
for (int count : counts) {
if (count != 0) return false;
}
return true;
}Use it when
Use fixed frequency arrays (int[26] or int[128]) when input characters belong to a known small alphabet. It runs 10x faster than HashMap with zero object allocation overhead.
Reconsider when
Use HashMap<Character, Integer> when dealing with arbitrary Unicode, non-English scripts, or sparse keys where a fixed-size array would waste memory.
String vs StringBuilder vs StringBuffer
Every software engineer must know when to transition from immutable String to mutable buffer classes. In Java and similar platforms, StringBuilder and StringBuffer provide dynamic character buffers, but they differ fundamentally in thread safety and synchronization cost.
| Feature | String | StringBuilder | StringBuffer |
|---|---|---|---|
| Mutability | Immutable (read-only) | Mutable (modifiable in-place) | Mutable (modifiable in-place) |
| Thread Safety | Thread-safe (cannot be modified) | Not thread-safe (unsynchronized) | Thread-safe (synchronized methods) |
| Performance | Fast for reads; slow for mutations | Fastest for single-threaded mutation | Slightly slower due to lock overhead |
| Memory Storage | String Constant Pool / Heap | Heap backing char/byte array | Heap backing char/byte array |
| Default Capacity | Exact length of characters | 16 characters + initial string length | 16 characters + initial string length |
| Growth Strategy | New allocation on modification | Doubles capacity + 2 (2 * old + 2) | Doubles capacity + 2 (2 * old + 2) |
Architectural Boundary · Do Not Confuse
StringBuilder: Default for DSA & Interviews
StringBuffer: Legacy Multithreaded Buffer
In 99.9% of coding interviews and single-threaded operations, ALWAYS use StringBuilder. StringBuffer methods are synchronized (acquiring and releasing intrinsic object locks), incurring needless CPU cycles when called by a single thread.
Compiler Constant Folding Optimization
String s = "a" + "b" + "c"; compiles directly into String s = "abc"; at bytecode compile time with ZERO runtime concatenation penalty. The O(n²) performance trap occurs only when concatenating dynamic variables inside loops at runtime.String Anatomy: Substring, Subsequence, Prefix & Suffix
Interview questions often hinge on precise structural terminology. Confusing a substring with a subsequence is an immediate disqualifier in technical interviews.
Original String: " a b c d e "
Indices: 0 1 2 3 4
1. SUBSTRING (Strictly Contiguous):
- Valid: "bcd" (indices 1..3 unbroken)
- Valid: "ab", "cde", "a"
- Invalid: "ace" (not contiguous!)
- Formula: A string of length n has exactly n*(n + 1)/2 non-empty substrings.
2. SUBSEQUENCE (Order-Preserving, Non-Contiguous Allowed):
- Valid: "ace" (delete 'b' and 'd', remaining order intact)
- Valid: "be", "acd", "abcde"
- Invalid: "ca" (violates relative ordering: 'c' appears after 'a')
- Formula: A string of length n has 2^n total subsequences (including empty).
3. PREFIX & SUFFIX:
- Prefix: Substring starting strictly at index 0 (e.g., "", "a", "ab", "abc")
- Suffix: Substring ending strictly at index n-1 (e.g., "e", "de", "cde", "abcde")Anagram
A permutation of characters: two strings with identical character frequencies in any order (e.g., "listen" and "silent").
Palindrome
A string that reads identically forwards and backwards: s[i] == s[n - 1 - i] for all i (e.g., "racecar").
Isomorphic Strings
Strings where characters in s can be mapped 1-to-1 to characters in t preserving order (e.g., "egg" and "add").
Lexicographical Order
Alphabetical dictionary order determined by comparing ASCII/Unicode numerical values character-by-character from left to right.
The String Rotation Doubling Trick
A classic question asks: "Given strings s1 and s2, determine if s2 is a cyclic rotation of s1 in O(n) time." (e.g., "waterbottle" rotated is "erbottlewat"). Instead of simulating all n rotation shifts, doubling s1 solves it in one shot.
s1 = "waterbottle"
s2 = "erbottlewat"
Concatenate s1 with itself:
s1 + s1 = "wat[erbottlewat]erbottle"
^ ^
s2 is found as a substring!
Why this works:
Every possible cyclic rotation of s1 starts at some index i and wraps around.
In s1 + s1, the wrap-around simply continues into the second copy of s1!public boolean isRotation(String s1, String s2) {
// Both strings must be non-null and equal length
if (s1 == null || s2 == null || s1.length() != s2.length()) {
return false;
}
// (s1 + s1) contains every valid cyclic rotation of s1
String doubled = s1 + s1;
return doubled.contains(s2);
}Active Recall Checkpoint
What is the time complexity of the doubling rotation check (s1 + s1).contains(s2) for strings of length n?
Hint: Consider the cost of string concatenation and substring search.
Show answer & explanation
O(n) time and O(n) auxiliary space. Concatenating s1 + s1 takes O(2n) = O(n), and substring searching an n-length pattern in a 2n-length text using algorithms like KMP or Boyer-Moore takes O(n).
Substring Search Algorithms: Naive vs KMP vs Rabin-Karp
Finding whether pattern P of length m exists inside text T of length n is one of the foundational problems in computer science. Different algorithms trade off precomputation complexity against execution speed.
| Algorithm | Precomputation | Search Time | Space | Core Mechanism & Intuition |
|---|---|---|---|---|
| Naive Sliding Window | None: O(1) | O(n × m) | O(1) | Checks all starting positions; backtracks on mismatch |
| KMP (Knuth-Morris-Pratt) | O(m) LPS array | O(n) | O(m) | Longest Prefix Suffix (LPS) table skips redundant comparisons |
| Rabin-Karp | O(m) hash computation | O(n) average (O(nm) worst) | O(1) | Rolling polynomial hash; compares string only when hashes match |
| Boyer-Moore | O(m + σ) bad char table | O(n / m) best (sub-linear) | O(σ) | Scans pattern right-to-left; skips large blocks on mismatch |
| Z-Algorithm | O(n + m) Z-array | O(n + m) | O(n + m) | Computes length of longest common prefix with start at every index |
Unicode & The Length Trap: Surrogate Pairs
char (16 bits). However, emojis (e.g. "😀") and mathematical symbols require a 32-bit supplementary code point encoded as a surrogate pair of two 16-bit chars! Consequently, "😀".length() returns 2, not 1. To count real human glyphs, use s.codePointCount(0, s.length()).Cost Model
Complexity & Trade-offs
When to use it
- Contiguous data, strings, matrix grids, and in-place transformations.
- Problems where indexes encode processed regions.
- Cache-friendly scans and compact storage.
Common mistakes
- Using a stale index after insertion, deletion, or a boundary move.
- Overwriting a value before it has been consumed.
- Assuming a matrix is square or a string is mutable.
Practice prompts
- Reproduce the worked String fundamentals example without looking at the trace.
- Change one input condition and explain whether the String fundamentals invariant still holds.
- Implement the approach, derive its cost, and compare it with a straightforward baseline.
Mastery check