← Back to DevBytes

Solving Longest Palindromic Substring in JavaScript: Step-by-Step Guide

Solving the Longest Palindromic Substring in JavaScript: A Step-by-Step Guide

The Longest Palindromic Substring problem is one of the most classic algorithmic challenges you will encounter in coding interviews and competitive programming. It asks a deceptively simple question: given a string, what is the longest contiguous substring that reads the same forwards and backwards? In this tutorial, we will break down the problem, explore multiple solution strategies, and implement an efficient solution in JavaScript.

What Is a Palindrome?

A palindrome is a sequence of characters that remains identical when reversed. For example, "racecar", "madam", and "abba" are all palindromes. A palindromic substring is a contiguous slice of a larger string that itself is a palindrome. Given an input like "babad", the longest palindromic substrings are "bab" and "aba", both of length 3.

Why This Problem Matters

This problem is a staple on platforms like LeetCode (Problem #5) and frequently appears in technical interviews at major tech companies. It matters for several reasons:

Understanding the Problem Statement

Given a string s of length n, return the longest palindromic substring in s. If multiple answers exist with the same maximum length, returning any one of them is acceptable.

For example:

Input:  "cbbd"
Output: "bb"

Input:  "a"
Output: "a"

Input:  "ac"
Output: "a"  (or "c", both are valid)

Constraints typically include 1 <= s.length <= 1000, and the string consists of printable ASCII characters. The challenge is to find an efficient solution rather than checking every possible substring naively.

Approach 1: Brute Force

The most intuitive approach is to generate every possible substring and check whether each one is a palindrome. While easy to understand, this approach is inefficient.

Implementation

function isPalindrome(s, left, right) {
  while (left < right) {
    if (s[left] !== s[right]) return false;
    left++;
    right--;
  }
  return true;
}

function longestPalindromeBruteForce(s) {
  let longest = "";
  for (let i = 0; i < s.length; i++) {
    for (let j = i; j < s.length; j++) {
      if (isPalindrome(s, i, j)) {
        const substring = s.slice(i, j + 1);
        if (substring.length > longest.length) {
          longest = substring;
        }
      }
    }
  }
  return longest;
}

console.log(longestPalindromeBruteForce("babad")); // "bab" or "aba"

Complexity Analysis

There are O(n^2) substrings, and checking each one takes O(n) time, giving a total time complexity of O(n^3). The space complexity is O(1) (excluding the output). For strings longer than a few hundred characters, this becomes impractical.

Approach 2: Expand Around Center

A palindrome mirrors around its center. For a string of length n, there are 2n - 1 possible centers — one for each character (odd-length palindromes) and one between each pair of adjacent characters (even-length palindromes). By expanding outward from each center, we can find the longest palindrome in O(n^2) time and O(1) space.

Step-by-Step Logic

Implementation

function expandAroundCenter(s, left, right) {
  while (left >= 0 && right < s.length && s[left] === s[right]) {
    left--;
    right++;
  }
  // Return the length of the palindrome
  return right - left - 1;
}

function longestPalindrome(s) {
  if (!s || s.length < 1) return "";

  let start = 0;
  let end = 0;

  for (let i = 0; i < s.length; i++) {
    const oddLength = expandAroundCenter(s, i, i);     // odd-length palindrome
    const evenLength = expandAroundCenter(s, i, i + 1); // even-length palindrome
    const maxLength = Math.max(oddLength, evenLength);

    if (maxLength > end - start + 1) {
      start = i - Math.floor((maxLength - 1) / 2);
      end = i + Math.floor(maxLength / 2);
    }
  }

  return s.slice(start, end + 1);
}

console.log(longestPalindrome("babad")); // "bab" or "aba"
console.log(longestPalindrome("cbbd"));  // "bb"
console.log(longestPalindrome("a"));     // "a"
console.log(longestPalindrome("racecar")); // "racecar"

How the Index Math Works

After expansion, the palindrome spans from index start to end inclusive. Given the center index i and the palindrome length maxLength:

This formula works for both odd and even length palindromes because the floor division naturally handles the asymmetry of even-length centers.

Complexity Analysis

Time complexity is O(n^2) since expanding around a center can take up to O(n) time, and we do this for 2n - 1 centers. Space complexity is O(1), making this approach both efficient and memory-friendly. For most interview scenarios, this is the recommended solution.

Approach 3: Dynamic Programming

Dynamic programming (DP) offers another O(n^2) solution but uses O(n^2) space. The idea is to build a table dp[i][j] that is true if the substring s[i..j] is a palindrome. A substring s[i..j] is a palindrome if s[i] === s[j] and the inner substring s[i+1..j-1] is also a palindrome (or the length is at most 2).

Implementation

function longestPalindromeDP(s) {
  const n = s.length;
  if (n < 1) return "";

  // dp[i][j] = true if s[i..j] is a palindrome
  const dp = Array.from({ length: n }, () => new Array(n).fill(false));

  let start = 0;
  let maxLength = 1;

  // Every single character is a palindrome
  for (let i = 0; i < n; i++) {
    dp[i][i] = true;
  }

  // Check two-character substrings
  for (let i = 0; i < n - 1; i++) {
    if (s[i] === s[i + 1]) {
      dp[i][i + 1] = true;
      start = i;
      maxLength = 2;
    }
  }

  // Check substrings of length 3 and greater
  for (let len = 3; len <= n; len++) {
    for (let i = 0; i <= n - len; i++) {
      const j = i + len - 1;
      if (s[i] === s[j] && dp[i + 1][j - 1]) {
        dp[i][j] = true;
        start = i;
        maxLength = len;
      }
    }
  }

  return s.slice(start, start + maxLength);
}

console.log(longestPalindromeDP("babad")); // "bab" or "aba"
console.log(longestPalindromeDP("cbbd"));  // "bb"

Complexity Analysis

Time complexity is O(n^2) because we fill an n x n table. Space complexity is also O(n^2) due to the DP table. While this approach is more memory-intensive than expand-around-center, it is valuable for understanding DP fundamentals and can be extended to related problems like counting palindromic substrings.

Approach 4: Manacher's Algorithm

For those seeking the theoretically optimal solution, Manacher's Algorithm solves the problem in O(n) time. It works by transforming the string to handle even-length palindromes uniformly, then uses previously computed palindrome radii to skip redundant comparisons. While powerful, it is complex to implement and rarely expected in interviews unless explicitly requested.

function longestPalindromeManacher(s) {
  if (!s || s.length < 1) return "";

  // Transform s into T, e.g. "abc" -> "^#a#b#c#$"
  const T = "^#" + s.split("").join("#") + "#$";
  const n = T.length;
  const P = new Array(n).fill(0);
  let C = 0, R = 0;

  for (let i = 1; i < n - 1; i++) {
    const mirror = 2 * C - i;
    if (i < R) {
      P[i] = Math.min(R - i, P[mirror]);
    }

    // Expand around center i
    while (T[i + P[i] + 1] === T[i - P[i] - 1]) {
      P[i]++;
    }

    // Update center and right boundary
    if (i + P[i] > R) {
      C = i;
      R = i + P[i];
    }
  }

  // Find the maximum element in P
  let maxLen = 0;
  let centerIndex = 0;
  for (let i = 1; i < n - 1; i++) {
    if (P[i] > maxLen) {
      maxLen = P[i];
      centerIndex = i;
    }
  }

  const start = Math.floor((centerIndex - maxLen) / 2);
  return s.slice(start, start + maxLen);
}

console.log(longestPalindromeManacher("babad")); // "bab" or "aba"
console.log(longestPalindromeManacher("cbbd"));  // "bb"

Manacher's Algorithm is an impressive optimization but should be reserved for cases where O(n) performance is genuinely required, such as processing very large strings in production systems.

Comparing the Approaches

Best Practices

Testing Your Solution

Robust testing ensures your implementation handles edge cases correctly. Here is a test suite covering common scenarios:

function runTests() {
  const testCases = [
    { input: "babad", expectedLength: 3 },
    { input: "cbbd", expectedLength: 2 },
    { input: "a", expectedLength: 1 },
    { input: "ac", expectedLength: 1 },
    { input: "racecar", expectedLength: 7 },
    { input: "abba", expectedLength: 4 },
    { input: "aaaa", expectedLength: 4 },
    { input: "abcde", expectedLength: 1 },
    { input: "", expectedLength: 0 },
  ];

  testCases.forEach(({ input, expectedLength }) => {
    const result = longestPalindrome(input);
    const passed = result.length === expectedLength;
    console.log(
      `Input: "${input}" | Result: "${result}" | ` +
      `Expected length: ${expectedLength} | ${passed ? "PASS" : "FAIL"}`
    );
  });
}

runTests();

Common Pitfalls

Conclusion

The Longest Palindromic Substring problem is a fantastic exercise in algorithmic problem-solving that rewards careful analysis of trade-offs. While the brute force approach is a natural starting point, the expand-around-center technique offers the best combination of efficiency, clarity, and interview readiness. Dynamic programming provides a valuable learning opportunity for table-based reasoning, and Manacher's Algorithm demonstrates how advanced techniques can push performance to linear time. By mastering these approaches, understanding their complexities, and following best practices around edge cases and testing, you will be well-equipped to tackle this problem confidently in any technical interview or real-world application.

— Ad —

Google AdSense will appear here after approval

← Back to all articles