- Home
- /
- Tutorials
- /
- DSA Tutorial
- /
- Strings in JavaScript for DSA
Strings
Strings in JavaScript for DSA
JavaScript strings cannot be changed. Every operation that looks like editing one is really building a new one, and that single fact explains most string performance problems.
Immutable means copied
You cannot assign to a character - s[0] = "x" silently does nothing. Any change produces a whole new string.
Building a string, the slow way
javascript
// A new string on every iteration.
function repeatSlow(char, times) {
let out = ""
for (let i = 0; i < times; i++) out += char
return out
}
// Collect the pieces, join once at the end.
function repeatFast(char, times) {
const parts = []
for (let i = 0; i < times; i++) parts.push(char)
return parts.join("")
}
console.log(repeatSlow("ab", 3)) // "ababab"
console.log(repeatFast("ab", 3)) // "abababModern engines optimise simple += loops well, so you may not see a difference on small inputs. The array-and-join version is predictable at every size, which is why it is the one to reach for.
Costs worth memorising
s[i]ands.length- O(1).s.slice(a, b)- O(n), it copies.s.split("")- O(n) time and space.s.includes(t)andindexOf- O(n × m) worst case.- Comparing with
===- O(n), character by character.
Counting characters
Most string problems reduce to counting. A Map is the general tool:
Frequency and anagrams
javascript
function counts(s) {
const map = new Map()
for (const ch of s) {
map.set(ch, (map.get(ch) || 0) + 1)
}
return map
}
function isAnagram(a, b) {
if (a.length !== b.length) return false
const seen = counts(a)
for (const ch of b) {
if (!seen.has(ch)) return false
seen.set(ch, seen.get(ch) - 1)
if (seen.get(ch) === 0) seen.delete(ch)
}
return seen.size === 0
}
console.log(isAnagram("listen", "silent")) // trueThe length check first is not decoration - it turns an impossible case into an instant no.
One thing that will catch you out
"café".length can be 4 or 5 depending on how the é is encoded, and an emoji can report a length of 2. Iterating with for...of walks code points and is usually what you want; indexing with s[i] walks code units and is not.
Where length lies
javascript
const s = "a😀b"
console.log(s.length) // 4 — the emoji counts as 2
console.log([...s].length) // 3 — what a person would say
console.log(s[1]) // half of the emoji, on its ownInterview inputs are usually plain ASCII, so this rarely bites there. Real user input is not, and it does.
Comparing without allocating
Sorting characters to test for an anagram - [...a].sort().join("") - is O(n log n) and allocates twice. Counting is O(n) and allocates once. On interview-sized inputs both pass, but the counting version is the one that shows you know the difference.
Building output
When you construct a result character by character, push into an array and join once at the end. The reason is not style: each += conceptually produces a new string, and while engines optimise the simple case, they stop doing so as soon as the loop gets complicated.
Two habits worth keeping
javascript
// Reversing words in a sentence without repeated concatenation.
function reverseWords(sentence) {
return sentence.trim().split(/\s+/).reverse().join(" ")
}
// Comparing case-insensitively without building lowercase copies twice.
function equalsIgnoreCase(a, b) {
return a.length === b.length && a.toLowerCase() === b.toLowerCase()
}
console.log(reverseWords(" the quick brown fox ")) // "fox brown quick the"The length check before the comparison is the same trick as in the anagram test: it turns the common failing case into a single integer comparison.
