Reading
Arrays and Immutable Strings
Understand the JavaScript operations beneath array and string interview solutions.
Learning objectives
- Explain the cost of common array operations
- Avoid numeric sorting and mutation traps in JavaScript
- Work safely with immutable strings
- Select an iteration style that matches the needed information
Arrays are indexed sequences with tradeoffs
An array gives each element a numbered position. Reading values[i] is constant time
because the position directly identifies the slot. Work at the end is usually cheap;
work at the front or middle often moves everything after it.
| Operation | Typical cost | Reason |
|---|---|---|
arr[i] read/write | O(1) | direct position |
push / pop | amortized O(1) | changes the end |
shift / unshift | O(n) | remaining elements move |
includes / indexOf | O(n) | may scan all elements |
slice / spread | O(n) | creates and fills a copy |
sort | O(n log n) | comparison sorting |
Dynamic arrays occasionally need a larger backing store. That is why push is described
as amortized rather than worst-case O(1): one append may trigger a copy, but a long series
of appends still averages constant work per operation.
Mutation versus copying
JavaScript methods do not all behave alike:
sort,reverse,splice,push, andpopmutate the array.slice,map,filter, and spread create new arrays.
Mutation is useful when a problem requires O(1) auxiliary space, but it can surprise a caller that expected the original input to remain intact. State your choice explicitly.
Strings are values, not editable character arrays
You can read text[i], but you cannot assign a character back into the string. Operations
such as slice, toLowerCase, and concatenation produce new strings.
Repeated result += character can create and copy a growing string many times. Modern
engines optimize some cases, but an interview analysis should not depend on an invisible
optimization. For predictable linear construction, collect pieces and join once:
function alternatingCase(input: string): string {
const parts: string[] = []
for (let i = 0; i < input.length; i += 1) {
parts.push(i % 2 === 0 ? input[i].toUpperCase() : input[i].toLowerCase())
}
return parts.join("")
} Choose the loop by the information you need
Use for...of when values are enough. Use an index loop when you need neighbors,
positions, two moving boundaries, or an index in the answer.
Longest Common Prefix (LeetCode #14) gives an array of strings and asks for the longest starting substring shared by all of them. A column-by-column scan needs both the character position and each word:
function longestCommonPrefix(words: string[]): string {
if (words.length === 0) return ""
for (let column = 0; column < words[0].length; column += 1) {
const expected = words[0][column]
for (let row = 1; row < words.length; row += 1) {
if (column >= words[row].length || words[row][column] !== expected) {
return words[0].slice(0, column)
}
}
}
return words[0]
} If there are w words and the shortest relevant prefix has length k, the scan is
O(w · k). Describing it as O(n) hides the two dimensions.
Character codes and alphabets
character.charCodeAt(0) - 97 maps lowercase English a..z to 0..25. A fixed array
of 26 counts can be faster and simpler than a Map only when the input contract really
guarantees that alphabet. Unicode text, uppercase letters, punctuation, or emojis make
that assumption incorrect. General solutions should prefer a Map unless constraints
justify the fixed alphabet.
Common mistakes
- Calling
shift()repeatedly to simulate a queue and accidentally creating O(n²) work. - Mutating a caller’s array through
sort()orreverse()without acknowledging it. - Forgetting the comparator for numeric sorting.
- Treating strings as mutable arrays.
- Using
for...ofand then searching again for an index you could have retained.
Price the operations
Explain the cost and mutation behavior of push, shift, slice, sort, and string
concatenation. Then explain why the prefix scan stops at the first mismatch.