Part 1 · Lesson 9 · 20 min read
Strings
Text behaves like an array of characters that cannot be changed in place. Looping over characters, reversing, palindromes and counting.
On this page
A string is a row of characters
A string is a piece of text, such as "hello". A character is one single letter, digit, space or punctuation mark.
A string works almost like an array of characters. Each character sits at an index, and the index starts at 0. The length property tells you how many characters there are. (One small detail: JavaScript counts in 16-bit units. Most emoji take two units, so their length is 2. You do not need to worry about this in this series.)
So everything from Lesson 8 works on strings too: index loops, best-so-far, counting and two pointers. Strings are, after arrays, one of the most common interview topics. Typical questions are palindromes, anagrams, reversing words and counting characters. (A palindrome reads the same forwards and backwards, like “madam”. An anagram is a word made by re-ordering the letters of another word, like “listen” and “silent”.)
Reading characters
const word = "hello";console.log(word.length); // 5console.log(word[0]); // hconsole.log(word[word.length - 1]); // oconsole.log(word[1] + word[2]); // elYou can't change a string — build a new one
This is the main difference from arrays. Strings are immutable. Immutable means “cannot be changed after it is made”. You can read word[0], but you cannot replace it. Every “change” really builds a new string. (The opposite is mutable: an array is mutable, so you can change its items.)
let word = "cat";word[0] = "b"; // does nothing (in strict mode it throws a TypeError)console.log(word); // catword = "b" + word.slice(1); // build a NEW string and store itconsole.log(word); // batYou have two choices. First, build a new string with += (as in the trace below). Second, turn the string into an array with s.split(""), change the array, and join it back with arr.join("").
Looping over a string
const s = "abc";for (let i = 0; i < s.length; i++) { console.log(i, s[i]);}for (const ch of s) { console.log(ch);}/* Output:0 a1 b2 cabc*/Traced: reversing a word
Walk from the last index down to 0. Join each character onto a new string. The empty string "" is the starting value, just like 0 is the starting value for a sum. Joining two strings with + or += is called concatenation.
Indices 0, 1, 2 hold c, a, t.
▶const s = "cat";2let rev = "";3for (let i = s.length - 1; i >= 0; i--) {4 rev += s[i];5}6console.log(rev);| s | "cat" |
Checking what kind of character it is
You can compare characters with < and >. Letters compare in alphabetical order. All uppercase letters come before all lowercase letters. This gives simple checks that you will use all the time:
const ch = "G";console.log(ch >= "a" && ch <= "z"); // false lowercase letter?console.log(ch >= "A" && ch <= "Z"); // true uppercase letter?console.log("7" >= "0" && "7" <= "9"); // true digit?console.log("aeiou".includes("e")); // true is it one of these?console.log(ch.toLowerCase()); // gCharacter codes
A computer stores only numbers. So every character is stored as a number. This number is called its character code. For example, “a” is 97 and “b” is 98. The letters are numbered in alphabetical order. This allows two useful tricks: finding a letter's position in the alphabet, and moving forward or backward in the alphabet.
console.log("a".charCodeAt(0)); // 97 every character has a number (its code)console.log("b".charCodeAt(0)); // 98 letters are numbered in orderconsole.log(String.fromCharCode(99)); // c and back again// Position of a lowercase letter in the alphabet (0 to 25):const ch = "e";console.log(ch.charCodeAt(0) - "a".charCodeAt(0)); // 4// Shift a letter forward by 2 places (a simple cipher):console.log(String.fromCharCode("x".charCodeAt(0) + 2)); // zThe position trick is code − code of "a". It gives 0 for “a”, 1 for “b” and so on. With it you can count letters in an array of 26 counters instead of a Map (a Map is a lookup table, see Lesson 10). You will use this in Lesson 15 and in many anagram questions.
The handful of built-in methods worth knowing
A built-in method is a function that JavaScript already gives to every string. You call it with a dot. The code below shows the ones you will meet most. Note that every one of them returns a new value. None of them changes the original string.
const s = " Hello World ";console.log(s.trim()); // Hello World remove spaces at both endsconsole.log(s.trim().toUpperCase()); // HELLO WORLD methods can be chainedconsole.log("banana".indexOf("an")); // 1 first position, or -1console.log("banana".includes("nan")); // trueconsole.log("hello".slice(1, 4)); // ell from index 1 up to (not including) 4console.log("a,b,c".split(",")); // [ 'a', 'b', 'c' ] string → arrayconsole.log(["a", "b", "c"].join("-")); // a-b-c array → stringtrim()removes spaces from both ends.toUpperCase()andtoLowerCase()change the letter case.indexOf(text)gives the position where the text first appears, or -1.includes(text)gives true or false.slice(start, end)gives a piece of the string. It includesstartand does not includeend.split(separator)cuts a string into an array of pieces.join(separator)does the opposite: it glues an array of strings into one string.
Use them for small jobs, such as lower-casing, trimming or splitting. But sometimes the question is the method, for example “reverse a string” or “find a substring”. Then the interviewer wants to see your loop. If you are not sure, ask: “May I use split and join?”
Practice questions
Count the vowels (a, e, i, o, u — either case) in a string.
Hint 1
Hint 2
"aeiou".includes(ch) tells you whether ch is a vowel.Show all 3 solutions with explanations
Count each character that appears in "aeiou".
function countVowels(s) { let count = 0; for (const ch of s.toLowerCase()) { if ("aeiou".includes(ch)) count++; } return count;}console.log(countVowels("Hello World")); // 3console.log(countVowels("sky")); // 0"aeiou".includes(ch) asks “is ch one of these five letters?” You do not need five separate comparisons.
A Set is a collection of unique values (Lesson 10). Put the vowels in a Set. Check each character with has, which returns true if the value is in the Set.
const VOWELS = new Set(["a", "e", "i", "o", "u"]);function countVowels(s) { let count = 0; for (const ch of s.toLowerCase()) if (VOWELS.has(ch)) count++; return count;}console.log(countVowels("Hello World")); // 3This is the same idea. A Set lookup takes about the same short time however many values the Set holds. This helps when the list of allowed characters is long.
A regular expression (regex) is a short pattern that describes text to find. The pattern /[aeiou]/gi finds every vowel at once.
function countVowels(s) { const matches = s.match(/[aeiou]/gi); // null if there are none return matches ? matches.length : 0;}console.log(countVowels("Hello World")); // 3console.log(countVowels("sky")); // 0[aeiou] matches any one vowel. The flag g (global) finds all matches. The flag i ignores letter case. This is short, but regular expressions are a separate skill. Interviewers usually prefer the loop.
Use Approach 1 in interviews. It is short and you need no extra knowledge.
Return the string reversed.
Hint 1
Hint 2
Show all 3 solutions with explanations
Start with an empty string. Add the characters from the last one to the first one.
function reverseString(s) { let rev = ""; for (let i = s.length - 1; i >= 0; i--) { rev += s[i]; } return rev;}console.log(reverseString("hello")); // ollehThis is the traced example from this lesson.
Turn the text into an array of characters. Reverse the array. Join it back into a string.
const reverseString = (s) => s.split("").reverse().join("");console.log(reverseString("hello")); // ollehThis is the standard one-line answer in everyday JavaScript.
LeetCode 344 gives the string as an array of characters. It asks you to reverse it in place. Swap the two ends, then move inwards.
function reverseChars(chars) { let left = 0, right = chars.length - 1; while (left < right) { [chars[left], chars[right]] = [chars[right], chars[left]]; left++; right--; } return chars;}console.log(reverseChars(["h", "e", "l", "l", "o"]).join("")); // ollehThis is the same two-pointer swap as reversing an array in Lesson 8. You can change an array in place, but you cannot change a string in place.
If the question is “reverse a string”, write Approach 1 or 3. The interviewer wants to see the loop. In real code, Approach 2 is fine.
Return true if the string reads the same forwards and backwards, ignoring case.
Hint 1
Hint 2
Show all 2 solutions with explanations
Compare the characters from both ends and move inwards. If one pair does not match, the answer is false.
function isPalindrome(s) { s = s.toLowerCase(); let left = 0, right = s.length - 1; while (left < right) { if (s[left] !== s[right]) return false; left++; right--; } return true;}console.log(isPalindrome("madam")); // trueconsole.log(isPalindrome("Racecar")); // trueconsole.log(isPalindrome("hello")); // false| left | right | s[left] | s[right] | equal? |
|---|---|---|---|---|
| 0 | 4 | h | o | no, so return false |
A palindrome is equal to its own reverse.
function isPalindrome(s) { s = s.toLowerCase(); return s === s.split("").reverse().join("");}console.log(isPalindrome("Racecar")); // trueThis is very short. But it always builds a full reversed copy, even when the first and last characters already differ.
Approach 1 is better. It makes no extra string, and it stops at the first mismatch.
Count the uppercase letters, lowercase letters and digits in a string.
Hint 1
Hint 2
ch >= "A" && ch <= "Z" (&& means “and”). Lowercase letters and digits work the same way.Show all 2 solutions with explanations
For each group (A to Z, a to z, 0 to 9), check that the character is between the first and the last character of the group.
function classify(s) { let upper = 0, lower = 0, digits = 0; for (const ch of s) { if (ch >= "A" && ch <= "Z") upper++; else if (ch >= "a" && ch <= "z") lower++; else if (ch >= "0" && ch <= "9") digits++; } return "upper " + upper + ", lower " + lower + ", digits " + digits;}console.log(classify("Hello123")); // upper 1, lower 4, digits 3Any other character (a space or punctuation) matches no branch, so it is not counted.
A letter is uppercase if it is different from its lowercase form. A letter is lowercase if it is different from its uppercase form.
function classify(s) { let upper = 0, lower = 0, digits = 0; for (const ch of s) { if (ch !== ch.toLowerCase()) upper++; else if (ch !== ch.toUpperCase()) lower++; else if (ch >= "0" && ch <= "9") digits++; } return "upper " + upper + ", lower " + lower + ", digits " + digits;}console.log(classify("Hello123")); // upper 1, lower 4, digits 3This version also works for letters outside A to Z, such as “É”.
Approach 1 is the usual answer when the input is plain English letters. Approach 2 works in more cases.
Make the first letter of every word uppercase.
Hint 1
Show all 2 solutions with explanations
Copy each character into a new string. Change it to upper case when it starts a word.
function capitalise(s) { let result = ""; for (let i = 0; i < s.length; i++) { const startsWord = i === 0 || s[i - 1] === " "; result += startsWord ? s[i].toUpperCase() : s[i]; } return result;}console.log(capitalise("hello big world")); // Hello Big WorldStrings cannot be edited, so we build a new one. Looking at the previous character (s[i - 1]) is the same neighbour check as in “is the array sorted”. The form condition ? a : b means “if the condition is true use a, otherwise use b”.
Split the text into words. Capitalise each word. Join the words with spaces.
function capitalise(s) { return s .split(" ") .map((w) => (w === "" ? w : w[0].toUpperCase() + w.slice(1))) .join(" ");}console.log(capitalise("hello big world")); // Hello Big Worldw[0].toUpperCase() + w.slice(1) is the first letter in upper case followed by the rest of the word. The w === "" check protects against double spaces, which make empty pieces.
Both are correct. Approach 2 reads more naturally. Approach 1 shows the character-by-character thinking that interviewers test.
Count the words in a sentence that may have extra spaces anywhere.
Hint 1
Hint 2
Show all 3 solutions with explanations
A word starts at a non-space character whose previous character is a space (or that is the first character).
function countWords(s) { let count = 0; for (let i = 0; i < s.length; i++) { if (s[i] !== " " && (i === 0 || s[i - 1] === " ")) count++; } return count;}console.log(countWords("the sky is blue")); // 4console.log(countWords(" the sky is blue ")); // 4console.log(countWords(" ")); // 0Every word has exactly one start, however many spaces are around it.
Split the text at every space. Then remove the empty strings that extra spaces create.
const countWords = (s) => s.split(" ").filter((w) => w !== "").length;console.log(countWords(" the sky is blue ")); // 4Splitting "a b" (two spaces) at a single space gives ["a", "", "b"]. If you remove the empty pieces, only real words are left.
Remove the spaces at both ends. Then split at “one or more spaces” with the regular expression /\s+/.
function countWords(s) { const t = s.trim(); return t === "" ? 0 : t.split(/\s+/).length;}console.log(countWords(" the sky is blue ")); // 4console.log(countWords(" ")); // 0\s+ means “one or more whitespace characters”, so double spaces and tabs also work. The empty-string check is needed because "".split(…) returns an array with one empty piece.
Approach 1 needs no built-in methods and handles every case. Approach 2 is the clearest version that uses built-in methods.
Return the longest word in a sentence of single-space-separated words.
Hint 1
Show all 2 solutions with explanations
Keep the longest word you have seen so far. Replace it when a longer word appears.
function longestWord(s) { let best = ""; for (const w of s.split(" ")) { if (w.length > best.length) best = w; } return best;}console.log(longestWord("I love programming a lot")); // programmingconsole.log(longestWord("cat dog")); // catWe use > and not >=. So when two words have the same length, the first one is kept.
reduce (Lesson 8) combines all items into one value. Here it keeps the longer word at each step.
const longestWord = (s) => s.split(" ").reduce((best, w) => (w.length > best.length ? w : best), "");console.log(longestWord("I love programming a lot")); // programmingThis is the same logic in one expression.
Reverse the order of words. Collapse multiple spaces into one and remove spaces at the ends. (LeetCode 151.)
Hint 1
Hint 2
Show all 2 solutions with explanations
Make a clean list of words. Reverse the list. Join it with single spaces.
function reverseWords(s) { return s .split(" ") .filter((w) => w !== "") .reverse() .join(" ");}console.log(reverseWords("the sky is blue")); // blue is sky theconsole.log(reverseWords(" hello world ")); // world helloEach step does one clear job. This is the most common accepted answer for LeetCode 151.
Walk backwards through the string. Skip spaces. When you reach the end of a word, read the whole word and add it to the result.
function reverseWords(s) { const words = []; let i = s.length - 1; while (i >= 0) { while (i >= 0 && s[i] === " ") i--; // skip spaces if (i < 0) break; const end = i; while (i >= 0 && s[i] !== " ") i--; // move to the start of the word words.push(s.slice(i + 1, end + 1)); } return words.join(" ");}console.log(reverseWords(" hello world ")); // world helloThis shows the character-by-character work that the built-in methods hide. Some interviewers ask for it as a follow-up (“without using split”).
Start with Approach 1. Be ready to explain Approach 2 if the interviewer asks you not to use split.
Replace each run of repeated characters with the character followed by its count.
Hint 1
Hint 2
Show all 2 solutions with explanations
Compare each character with the previous one. If it is the same, the run continues. If it is different, write out the finished run.
function compress(s) { if (s.length === 0) return ""; let result = ""; let count = 1; for (let i = 1; i <= s.length; i++) { if (i < s.length && s[i] === s[i - 1]) { count++; } else { result += s[i - 1] + count; // the run has ended count = 1; } } return result;}console.log(compress("aaabbc")); // a3b2c1console.log(compress("abc")); // a1b1c1| i | s[i] vs s[i−1] | action | result |
|---|---|---|---|
| 1 | a = a | count = 2 | |
| 2 | a = a | count = 3 | |
| 3 | b ≠ a | write a3 | a3 |
| 4 | b = b | count = 2 | a3 |
| 5 | c ≠ b | write b2 | a3b2 |
| 6 | end | write c1 | a3b2c1 |
Pointer i marks the start of a run. Move pointer j forward while the character stays the same. The run length is j − i.
function compress(s) { let result = ""; let i = 0; while (i < s.length) { let j = i; while (j < s.length && s[j] === s[i]) j++; result += s[i] + (j - i); i = j; // the next run starts where this one ended } return result;}console.log(compress("aaabbc")); // a3b2c1Each run is handled in one step, so the last run needs no special case. Many people find this version easier to get right.
Both are correct. Approach 2 avoids the common bug of forgetting the last run, so it is a good first choice.
Ignoring case and every character that is not a letter or digit, is the string a palindrome? (LeetCode 125.)
Hint 1
Hint 2
Show all 2 solutions with explanations
Move left and right inwards. Skip punctuation and spaces. Compare the other characters.
function isAlphaNum(ch) { return (ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9");}function isValidPalindrome(s) { s = s.toLowerCase(); let left = 0, right = s.length - 1; while (left < right) { if (!isAlphaNum(s[left])) { left++; continue; } if (!isAlphaNum(s[right])) { right--; continue; } if (s[left] !== s[right]) return false; left++; right--; } return true;}console.log(isValidPalindrome("A man, a plan, a canal: Panama")); // trueconsole.log(isValidPalindrome("race a car")); // falseNo new string is made. The word continue skips the rest of the current loop round and starts the next round. The other pointer does not move.
Build a new string with only lower-case letters and digits. Then check whether it equals its reverse.
function isValidPalindrome(s) { const clean = s.toLowerCase().replace(/[^a-z0-9]/g, ""); return clean === clean.split("").reverse().join("");}console.log(isValidPalindrome("A man, a plan, a canal: Panama")); // trueconsole.log(isValidPalindrome("race a car")); // false/[^a-z0-9]/g matches every character that is not a letter or digit (the ^ means “not”). replace removes them all. This is short and clear, but it builds two new strings.
Approach 2 is a fine first answer. For LeetCode 125, interviewers look for Approach 1, because it uses no extra memory.
Return the length of the last word in a string that may contain extra spaces. (LeetCode 58.)
Hint 1
Hint 2
Show all 2 solutions with explanations
Skip the spaces at the end. Then count letters until the next space.
function lengthOfLastWord(s) { let i = s.length - 1; while (i >= 0 && s[i] === " ") i--; // skip spaces at the end let length = 0; while (i >= 0 && s[i] !== " ") { length++; i--; } return length;}console.log(lengthOfLastWord("Hello World")); // 5console.log(lengthOfLastWord(" fly me to the moon ")); // 4Only the characters at the end are checked, so it does very little work even for a long string.
Remove the outer spaces. Split the text into words. Measure the last word.
function lengthOfLastWord(s) { const words = s.trim().split(/\s+/); return words[words.length - 1].length;}console.log(lengthOfLastWord(" fly me to the moon ")); // 4This is short and easy to read. But it splits the whole string only to read the last word.
Both pass LeetCode 58. Approach 1 is the efficient answer. Approach 2 is quicker to write.
Make it stick
- Explain “immutable” with the
"cat"→"bat"example. - Write the two-pointer palindrome check from memory.
- Dry-run
compress("aabccc"). (Answer: a2b1c3.)
Revisit schedule: solve two of the questions again tomorrow, again in 3 days, and once more in a week — without looking at the answers. Each time it will feel easier. That is the memory forming.
Next lesson: objects, Map and Set. These let you look things up by name. You will also learn the counting pattern that is behind many interview questions.
Practise on LeetCode
Real interview problems that use exactly what this lesson teaches. Solve them in JavaScript after the practice questions above.
Published by Jainik · Last updated · How these lessons are made
Keep learning
Related series