Strings in JavaScript
1. Introduction to String
JavaScript strings are used for storing and manipulating text. They can contain zero or more characters within quotes.
Syntax:
let string_name = "text";Example:
let x = "Welcome to GeeksforGeeks!";
console.log(x);Strings in JavaScript
- Quotes: You can use single or double quotes to write strings. Quotes can be used inside a string, as long as they don't match the quotes surrounding the string.
- Escape Characters: To use the same type of quotes within a string, or to insert special characters, use the backslash
\escape character. - String Length: The length of a string can be found using the built-in
.lengthproperty. - String Breaking: For ease of understanding, long strings can be broken across lines using the
\symbol. - Strings as Objects: Normally, strings are primitive values, but they can also be defined as objects using the keyword
new String().
let length = "GeeksforGeeks".length; // returns 13
let objStr = new String("Geeks"); // creates a string object2. Common String Interview Problems
Problem 1: Palindrome Check
A palindrome is a word, phrase, number, or other sequences of characters which reads the same forward and backward (ignoring spaces, punctuation, and capitalization).
Method 1: Using Two Pointers (Iterators)
Use two pointers, begin at the start and end at the last character. Compare characters while moving towards the center.
const isPalindrome = (input) => {
let begin = 0;
let end = input.length - 1;
while (begin < end) {
if (input[begin] !== input[end]) {
return false;
}
begin++;
end--;
}
return true;
};Method 2: Using a standard For Loop
const isPalindrome = (input) => {
for (let i = 0; i < input.length / 2; i++) {
if (input[i] !== input[input.length - 1 - i]) return false;
}
return true;
};Time Complexity: O(N)
Auxiliary Space: O(1)
Problem 2: Check if a string is a subsequence of another string
Given two strings s1 and s2, check if s2 is a subsequence of s1. A subsequence is a sequence that can be derived from another sequence by deleting some elements without changing the order of the remaining elements.
Iterative Solution: Use two pointers. Traverse both strings. If characters match, move both pointers. If not, move only the pointer for the first string.
const isSubSeq = (s1, s2, n, m) => {
let j = 0;
for (let i = 0; i < n && j < m; i++) {
if (s1[i] === s2[j]) {
j++;
}
}
// If we matched all characters of s2, j will be equal to m
return j === m;
};Time Complexity: O(N) where N is length of s1.
Auxiliary Space: O(1)
Recursive Solution:
const isSubSeqRec = (s1, s2, n, m) => {
if (m === 0) return true;
if (n === 0) return false;
if (s1[n - 1] === s2[m - 1]) {
return isSubSeqRec(s1, s2, n - 1, m - 1);
}
return isSubSeqRec(s1, s2, n - 1, m);
};Time Complexity: O(N)
Auxiliary Space: O(N) (due to recursive call stack).
Problem 3: Check for Anagram
Given two strings, check whether they are an anagram of each other. Two strings are anagrams if they are permutations of each other (contain the exact same characters, just in a different order).
Method 1: Sorting Sort both strings. If they are anagrams, the sorted strings will be identical.
const areAnagram = (str1, str2) => {
if (str1.length !== str2.length) return false;
// Sort both strings
let s1 = str1.split('').sort().join('');
let s2 = str2.split('').sort().join('');
return s1 === s2;
};Time Complexity: O(N log N)
Auxiliary Space: O(N) or O(1) depending on the sorting algorithm implementation, but in JS .split() creates arrays taking O(N) space.
Method 2: Frequency Counting (Optimal) Create an array of size 256 (for all ASCII characters) to maintain the frequency of characters. Increment count for characters in the first string and decrement for characters in the second string. If all counts are zero at the end, they are anagrams.
const NO_OF_CHARS = 256;
const areAnagramOptimized = (str1, str2) => {
if (str1.length !== str2.length) return false;
let count = new Array(NO_OF_CHARS).fill(0);
for (let i = 0; i < str1.length; i++) {
count[str1.charCodeAt(i)]++;
count[str2.charCodeAt(i)]--;
}
for (let i = 0; i < NO_OF_CHARS; i++) {
if (count[i] !== 0) return false;
}
return true;
};Time Complexity: O(N)
Auxiliary Space: O(1) (Array of size 256 is constant space).
Problem 4: Leftmost Repeating Character
Given a string, find the repeated character present first in the string. For example, in the string "geeksforgeeks", the first repeating character is 'g'.
Method 1: Tracking First Occurrence (Left to Right)
Keep track of the first occurrence of every character in an array firstIndex of size 256 initialized to -1. As we traverse from left to right, if the character is seen for the first time, store its index. If it repeats, compare its first index with the current result and update the result if the first index is smaller.
const NO_OF_CHARS = 256;
function firstRepeatingLeftToRight(str) {
let firstIndex = new Array(NO_OF_CHARS).fill(-1);
let res = Infinity;
for (let i = 0; i < str.length; i++) {
let charCode = str.charCodeAt(i);
// If character is seen first time
if (firstIndex[charCode] === -1) {
firstIndex[charCode] = i;
} else {
// If character repeats, update result with the minimum index
res = Math.min(res, firstIndex[charCode]);
}
}
return (res === Infinity) ? -1 : res;
}Time Complexity: O(N)
Auxiliary Space: O(1) (Fixed array of size 256).
Method 2: Reverse Traversal (Right to Left)
Initialize a visited boolean array of size 256. Traverse the string from right to left. If we see a character we haven't visited, mark it visited. If we see a character that is already visited, update the result index. The last updated result will be our leftmost repeating character.
function firstRepeatingRightToLeft(str) {
let visited = new Array(NO_OF_CHARS).fill(false);
let res = -1;
// Traverse from right and update res as soon as we see a visited character
for (let i = str.length - 1; i >= 0; i--) {
let charCode = str.charCodeAt(i);
if (visited[charCode] === false) {
visited[charCode] = true;
} else {
res = i;
}
}
return res;
}Time Complexity: O(N)
Auxiliary Space: O(1)
Problem 5: Reverse Words in a String
Given a string, reverse the order of words. For example, "i love programming very much" should become "much very programming love i".
Algorithm:
- Create a temporary array (
tmp) to store all words. - Iterate through the string character by character. Add characters to a temporary string (
str). - If a space
' 'is encountered, push the current word (str) into thetmparray and resetstrto empty. - After the loop, push the last word into the array.
- Finally, iterate backwards through the
tmparray to construct the reversed output string.
const reverseWords = (s) => {
let tmp = [];
let str = "";
// Iterate through the string to extract words
for (let i = 0; i < s.length; i++) {
if (s[i] === ' ') {
tmp.push(str);
str = "";
} else {
str += s[i];
}
}
// Push the last word
tmp.push(str);
let output = "";
// Traverse the words array backwards
for (let i = tmp.length - 1; i > 0; i--) {
output += tmp[i] + " ";
}
// Add the very first word without a trailing space
output += tmp[0];
console.log(output);
return output;
};
// Example usage:
// reverseWords("i love programming very much");Time Complexity: O(N) where N is the length of the string.
Auxiliary Space: O(N) to store the array of words and the new string.
3. Advanced String Patterns & Scenarios
To crack top-tier technical interviews, you must master these core architectural patterns for strings.
Pattern 1: The Sliding Window
The sliding window technique is used to track a subset of data in an array or string. It's the most common pattern for solving "longest/shortest substring" problems.
Scenario: Longest Substring Without Repeating Characters
Given a string s, find the length of the longest substring without repeating characters.
Approach: Use a Set to store the characters in the current window. Use two pointers (left and right). If s[right] is not in the set, add it and expand the window. If it is, remove s[left] and shrink the window from the left until the duplicate is gone.
const lengthOfLongestSubstring = (s) => {
let charSet = new Set();
let left = 0;
let maxLength = 0;
for (let right = 0; right < s.length; right++) {
// If we find a duplicate, shrink the window from the left
while (charSet.has(s[right])) {
charSet.delete(s[left]);
left++;
}
// Add the new character and calculate max length
charSet.add(s[right]);
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
};Time Complexity: O(N)
Auxiliary Space: O(min(N, M)) where M is the character set size (e.g., 26 or 256).
Pattern 2: Expand From Center (Two Pointers)
While standard two pointers move inwards from the edges, "Expand from Center" is used for finding palindromes within a larger string.
Scenario: Longest Palindromic Substring
Given a string s, return the longest palindromic substring in s.
Approach: A palindrome mirrors around its center. Therefore, a palindrome can be expanded from its center, and there are only 2N - 1 such centers (a center can be a single character, or between two characters for even-length palindromes).
const longestPalindrome = (s) => {
if (s == null || s.length < 1) return "";
let start = 0, end = 0;
const expandAroundCenter = (s, left, right) => {
while (left >= 0 && right < s.length && s[left] === s[right]) {
left--;
right++;
}
return right - left - 1; // Return length
};
for (let i = 0; i < s.length; i++) {
let len1 = expandAroundCenter(s, i, i); // Odd length
let len2 = expandAroundCenter(s, i, i + 1); // Even length
let len = Math.max(len1, len2);
if (len > end - start) {
start = i - Math.floor((len - 1) / 2);
end = i + Math.floor(len / 2);
}
}
return s.substring(start, end + 1);
};Time Complexity: O(N^2)
Auxiliary Space: O(1)
Pattern 3: Pattern Searching (Substring Matching)
Searching for a smaller string (pattern) within a larger string (text).
Scenario 1: Naive Pattern Searching
Slide the pattern over the text one by one and check for a match.
const naiveSearch = (text, pattern) => {
let n = text.length;
let m = pattern.length;
let indices = [];
for (let i = 0; i <= n - m; i++) {
let j;
for (j = 0; j < m; j++) {
if (text[i + j] !== pattern[j]) break;
}
if (j === m) indices.push(i); // Pattern found at index i
}
return indices;
};Time Complexity: O(N * M)
Scenario 2: KMP (Knuth-Morris-Pratt) Algorithm Overview
The naive approach is slow because it ignores matches done in previous iterations. KMP solves this by pre-processing the pattern to create an LPS (Longest Proper Prefix which is also Suffix) array. This array tells us how many characters to skip, allowing us to find the pattern in O(N + M) time without ever moving the text pointer backwards. (Note: KMP is an advanced algorithm and usually taught in a dedicated module).
Pattern 4: String Parsing and Edge Cases
Interviews often test your ability to handle edge cases (whitespace, signs, integer overflow).
Scenario: String to Integer (atoi)
Convert a string to a 32-bit signed integer. You must handle leading whitespace, a + or - sign, ignore trailing non-digit characters, and clamp the integer to the 32-bit range [-2^31, 2^31 - 1].
Approach: Iterate character by character and build the integer using math (res = res * 10 + digit).
const myAtoi = (s) => {
let i = 0;
let sign = 1;
let res = 0;
const INT_MAX = 2147483647;
const INT_MIN = -2147483648;
// 1. Ignore leading whitespace
while (s[i] === ' ') i++;
// 2. Check sign
if (s[i] === '-' || s[i] === '+') {
sign = s[i] === '-' ? -1 : 1;
i++;
}
// 3. Read digits
while (i < s.length && s[i] >= '0' && s[i] <= '9') {
let digit = s[i] - '0';
// 4. Handle overflow before it happens
if (res > Math.floor(INT_MAX / 10) || (res === Math.floor(INT_MAX / 10) && digit > 7)) {
return sign === 1 ? INT_MAX : INT_MIN;
}
res = res * 10 + digit;
i++;
}
return res * sign;
};Time Complexity: O(N)
Auxiliary Space: O(1)
4. The Ultimate String Problem Solving Framework
When facing a String problem in an interview, you can almost always solve it by categorizing it into one of these 7 core patterns. If you are stuck, mentally run through this checklist:
1. The Two-Pointer Technique
Used when you need to compare characters from different parts of a string.
- Inward Iteration: Moving from ends to the center (e.g., Valid Palindrome, Reverse String).
- Outward Iteration: Moving from the center to the ends (e.g., Longest Palindromic Substring).
- Same Direction: Fast and slow pointers (e.g., String Compression, Removing Duplicates).
2. The Sliding Window
Used when the problem asks for the "longest/shortest substring" or a "contiguous sequence" satisfying a specific condition.
- Fixed Size Window: Checking all substrings of length
k(e.g., Find All Anagrams in a String). - Variable Size Window: Expanding/shrinking based on a condition (e.g., Longest Substring Without Repeating Characters).
3. Frequency Counting (Hashing)
Used when the problem involves character frequencies, anagrams, or permutations.
- Use a Fixed Array of size 256 (for ASCII) or a JS Map/Object to store character counts in
O(1)space. - Classic Examples: Valid Anagram, First Unique Character, Group Anagrams.
4. Dynamic Programming (DP)
Used when the problem asks for the "longest/maximum" involving subsequences (not contiguous) or "minimum cost/operations".
- Classic Examples: Longest Common Subsequence (LCS), Edit Distance, Longest Palindromic Subsequence.
5. String Parsing & Stacks
Used when the string contains nested structures, mathematical equations, or when you need to match opening/closing sequences.
- Classic Examples: Valid Parentheses, Decode String, Basic Calculator.
6. Trie (Prefix Tree)
Used heavily in system design or advanced string problems involving dictionary matching, autocomplete, or prefixes.
- Classic Examples: Implement Trie, Word Search II, Longest Common Prefix.
7. Sorting
While you can't sort a string directly in JavaScript without converting it to an array (str.split('').sort().join('')), sorting is the go-to approach when checking if strings are identical in composition but not order.
- Classic Example: Group Anagrams (Sort each string and use it as a Hash Map key).