Strings as Data Structures: Mutability Across Languages
A string is an ordered sequence of characters — essentially an array of characters with a friendlier interface. You can index into it, scan it, and measure its length in O(1) to O(n) just like an array. The one detail that trips people up across languages is mutability: whether you can change a string in place. Getting that right is the difference between fast code and accidental O(n²) blowups.
A string is a sequence of characters
At its core, "hello" is the characters h, e, l, l, o laid out in order, indexed from 0. Reading a character by index is O(1); reading the length is O(1) in all four languages.
#include <string>
std::string s = "hello";
char c = s[1]; // 'e' — O(1) access
int n = s.size(); // 5 — O(1)
String s = "hello";
char c = s.charAt(1); // 'e' — O(1) access
int n = s.length(); // 5 — O(1)
const s = "hello";
const c = s[1]; // 'e' — O(1) access
const n = s.length; // 5 — O(1)
s = "hello"
c = s[1] # 'e' — O(1) access
n = len(s) # 5 — O(1)
The big difference: mutability
This is the part to internalize, because it changes how you write string algorithms in each language.
- C++
std::stringis mutable. You can assigns[i] = 'x'and modify characters in place. There’s also achar[]for low-level work. - Java
Stringis immutable. Every “modification” creates a new object. To build or edit a string efficiently, useStringBuilder. - JavaScript strings are immutable.
s[i] = 'x'silently does nothing. Build with an array of characters, thenjoin. - Python
stris immutable.s[i] = 'x'raises aTypeError. Convert to alist, edit, then"".join(...).
Here is the same task — change index 0 to an uppercase H — written idiomatically per language:
#include <string>
std::string s = "hello";
s[0] = 'H'; // mutable in place
// s is now "Hello"
String s = "hello";
StringBuilder sb = new StringBuilder(s);
sb.setCharAt(0, 'H'); // edit the builder, not the String
String result = sb.toString(); // "Hello"
let s = "hello";
const chars = s.split("");
chars[0] = "H"; // edit the array copy
s = chars.join(""); // "Hello"
s = "hello"
chars = list(s)
chars[0] = "H" # edit the list copy
s = "".join(chars) # "Hello"
Pitfall: Because Java, JavaScript, and Python strings are immutable, building a string by repeated
+=in a loop allocates a brand-new string each time — that’s O(n²) overall. Always accumulate into aStringBuilder/ array / list and join once at the end for O(n).
Why immutability exists
Immutable strings can be shared safely across threads and used as hash keys without fear of changing underneath you. The trade-off is that edits cost a copy. C++ chooses raw control (mutable), while Java, JS, and Python choose safety (immutable) — neither is “better,” just different defaults you must respect.
Going deeper: Characters are not always one byte. ASCII fits in a byte, but Unicode characters may span multiple code units. Java
charis a UTF-16 code unit, JavaScript indexes UTF-16 code units too, Python 3strindexes Unicode code points, and C++std::stringindexes bytes. For most DSA problems (ASCII inputs) this doesn’t bite, but be aware on multilingual text.
Complexity of common operations
| Operation | Complexity |
|---|---|
| Access char by index | O(1) |
| Length | O(1) |
| Concatenate two strings (lengths a, b) | O(a + b) |
| Substring of length m | O(m) |
| Search for a pattern (length m) | O(n · m) naive, O(n + m) with KMP |
| Compare two strings | O(min length) |
Where to go next
Now that you know how strings behave, learn the everyday toolkit — splitting, joining, substrings, and character frequency — in string manipulation. Then test yourself with the string exercises.