Foundations · Strings & characters
A string is characters in numbered slots
Index it like an array, because it is one. Every character in it is a number underneath, and that one fact turns case conversion, digit parsing and vowel checks into arithmetic.
Walk RECURSION one character at a time →01 The idea
One row of characters, numbered from zero
A string is text to you. To the machine it is a row of slots, one character per slot, numbered from 0. The word RECURSION is nine slots: slot 0 holds R, slot 1 holds E, slot 8 holds N. You read a slot with s[0], s[1], s[8] — the same square brackets you use on an array, because underneath it is an array.
A slot does not hold a letter shape either. It holds a small whole number: the character’s code. In ASCII, capital A is 65, so B is 66 and Z is 90. Small a is 97, so the same letter in the other case is exactly 32 higher. The codes inside each block run consecutively with no gaps, and that is what lets you do arithmetic on characters — c - 'A' turns a capital letter into its position 0 to 25, and c - '0' turns the character 7 into the number seven.
02 Worked example
Counting the vowels in RECURSION
Take RECURSION and count its vowels by hand, one slot at a time. One index walks from 0 to 8. At each stop you look at exactly one character and ask one question: is it A, E, I, O or U? A green cell means counted, a rose cell means checked and skipped, and the running count is on the right.
Nine stops, nine questions, one number at the end: 4. Notice what the walk never needed. It never looked back, never looked ahead, and never held more than one character at a time — so it costs O(n) time and O(1) extra space. Change the question at each stop from “is it a vowel” to “does it match the letter I am hunting for” and you have written linear search over a string without changing a single line of the loop.
03 Mechanics
Reversing a string, and what immutability costs
Now the half that catches people out. In Java, Python, C# and JavaScript a string cannot be changed after it is made — s.toUpperCase() does not modify s, it hands you a brand-new string. So reversing RECURSION into NOISRUCER means building something new. Both versions below walk the same nine slots from 8 down to 0. Only the writing differs, and the difference is not cosmetic.
Naive — glue a new string each turn
result = ""for i = length(s) - 1 down to 0: result = result + s[i] // a NEW stringreturn result
Builder — one buffer, filled in place
b = new builder()for i = length(s) - 1 down to 0: b.append(s[i]) // no copyreturn b.toString()
The assignment on line 3 was hiding an allocation. result = result + s[i] cannot extend result, because a string can never be extended. It allocates a fresh string one character longer and copies everything across. Turn k copies k characters, so the nine turns copy 1 + 2 + … + 9 = 45 characters to produce a 9-character answer.
The builder owns a buffer it is allowed to overwrite. append writes one character into spare capacity and moves a length marker — no copy, no new object. Nine appends cost nine writes, so the reverse drops from O(n²) to O(n). At 1,000 characters that is 1,000 writes instead of 500,500.
Neither version touched s. That is not politeness, it is the rule: the original string is unreachable for writing. If your language hands you a mutable character array instead, you can go further and swap the ends inwards — 0 with 8, 1 with 7, 2 with 6, 3 with 5, leaving slot 4 alone. Four swaps, no new memory at all.
05 Cheat sheet
The string operations you will be asked to cost
| Operation | Cost | What catches people out |
|---|---|---|
| Read one character, s[i] | O(1) | Valid indices are 0 to n − 1. s[n] is off the end: an exception in Java and Python, undefined in JavaScript, the NUL terminator in C. Never a usable character. |
| Length of a string | O(1) | Stored with the data in Java and Python. C keeps no length field, so strlen walks to the terminator and costs O(n). |
| Join two strings, s + t | O(n + m) | Always allocates a new string and copies both. Harmless once, ruinous in a loop. |
| s = s + c inside a loop | O(n²) | Each turn copies the whole prefix again — 45 character copies to build our 9-character reversal. |
| Builder append(c) | O(1) | Amortised: the buffer doubles when it fills. Nothing is a real string until you call toString or join. |
| substring(a, b) | O(b − a) | The end index is exclusive. substring(0, 3) of RECURSION is REC, not RECU. |
| s == t in Java | O(1) | Compares addresses, not characters. Use equals; when == works it is an accident of the literal pool. |
| Find a substring, indexOf(t) | O(n × m) | The plain scan retries the whole pattern at every start position. KMP is the linear answer. |
06 Where & why
Concatenation is not slow. Concatenation in a loop is.
Every language gives you + for strings and most of the time it is the right tool. What makes it dangerous is that the copy it performs is invisible in the source, so the cost only surfaces once the loop count grows. Here is where each option belongs.
Reach for plain concatenation when…
Use something else when…
| Situation | Better choice | Why |
|---|---|---|
| You are assembling text inside a loop | StringBuilder, or a list plus join | Concatenation recopies the whole prefix every turn: O(n²) writes against O(n) for the builder. |
| You need to change characters in place | A character array — char[] or list(s) | Strings cannot be written to at all. Convert once, mutate freely, convert back: two O(n) passes instead of n allocations. |
| You are gluing a list together with a separator | join | One pass, one allocation, and it handles the trailing separator that hand-written loops always get wrong. |
| The text is not plain English | Unicode-aware APIs | Indexing by byte or by UTF-16 unit can split a character in half. An accented letter or an emoji is not one code unit. |
07 Interview questions
What they actually ask
Strings open almost every coding round, and the follow-up is always about cost. Know why concatenation in a loop is quadratic and you have answered half of these before they are asked.
What is the difference between a character and a string?
What is ASCII, and why does character arithmetic work?
How do you flip a letter’s case without a library call?
What does it mean that strings are immutable?
Why is building a string in a loop O(n²)?
String versus StringBuilder versus StringBuffer?
How do you reverse a string in place?
Why does == sometimes work on Java strings and sometimes not?
How would you check whether a string is a palindrome?
Is finding a string’s length always O(1)?
Does one character always mean one byte?
Where would you actually use character arithmetic?
08 Practice problems
Six walks to do by hand first
For each one, write the slot numbers in a row and decide what the single question at each stop is. If you cannot say what one stop does, the loop will not rescue you.