Skip to main content

Longest Palindromic Substring

How to notice that every palindrome has a center and use that to avoid a heavier dynamic programming solution than necessary.

Given a string s, return the longest palindromic substring.

In the SeniorPath playground, if multiple answers have the same length, return the one that appears first in the string.

Example:

Input:  s = "babad"
Output: "bab"

What to notice before coding

Common mistakes

Step 1 - Separate substring from subsequence

The problem asks for a substring.

That means:

  • the characters must stay contiguous

So you are not allowed to skip letters in the middle.

Step 2 - Write the most direct baseline

The natural baseline is:

  • test every possible substring
  • check whether it is a palindrome
  • keep the longest one

It works.

But it repeats too much work.

Step 3 - Ask what every palindrome has in common

Every palindrome grows from a center.

That center can be:

  • a single letter, as in "aba"
  • the gap between two letters, as in "abba"

If you can expand from that center, you do not need to enumerate everything from scratch.

Step 4 - Turn that into expansion with two pointers

For each position in the string:

  • try odd center: (i, i)
  • try even center: (i, i + 1)

Keep expanding while both sides stay equal.

The interactive editor needs JavaScript. You can still read the prompt and copy the starter code below.

Starter code

export function longestPalindrome(s: string): string {
  // your solution here
  return ""
}
solution.ts
Stuck? Hints available.

Haven't solved it yet?

Viewing the solution now may reduce learning. It's worth trying a bit more.

Open the reference solution

Without JavaScript, the reference solution is shown inline instead of in a dialog.

Solution

Final complexity

Time: O(n^2)

Space: O(1)

Solution 1 - Test every substring O(n²)

Good baseline because it makes the high cost obvious.

function isPalindrome(candidate: string): boolean {
  let left = 0
  let right = candidate.length - 1

  while (left < right) {
    if (candidate[left] !== candidate[right]) {
      return false
    }

    left += 1
    right -= 1
  }

  return true
}

export function longestPalindrome(s: string): string {
  let best = s[0] ?? ""

  for (let start = 0; start < s.length; start += 1) {
    for (let end = start; end < s.length; end += 1) {
      const candidate = s.slice(start, end + 1)

      if (candidate.length > best.length && isPalindrome(candidate)) {
        best = candidate
      }
    }
  }

  return best
}

It works, but it compares too many substrings.

Solution 2 - Expand around the center with two pointers

Now you use the structure of palindromes instead of guessing all substrings blindly.

function expandAroundCenter(s: string, left: number, right: number): [number, number] {
  while (left >= 0 && right < s.length && s[left] === s[right]) {
    left -= 1
    right += 1
  }

  return [left + 1, right - 1]
}

export function longestPalindrome(s: string): string {
  if (s.length <= 1) {
    return s
  }

  let bestStart = 0
  let bestEnd = 0

  for (let index = 0; index < s.length; index += 1) {
    const odd = expandAroundCenter(s, index, index)
    const even = expandAroundCenter(s, index, index + 1)

    for (const [start, end] of [odd, even]) {
      const candidateLength = end - start + 1
      const bestLength = bestEnd - bestStart + 1

      if (candidateLength > bestLength || (candidateLength === bestLength && start < bestStart)) {
        bestStart = start
        bestEnd = end
      }
    }
  }

  return s.slice(bestStart, bestEnd + 1)
}

The strong insight here is choosing the right property: palindromes do not need 2D DP to exist. They only need a center.

What to say in the interview

A strong short explanation would be:

The baseline tests every substring and checks whether it is a palindrome, which is too expensive. The stronger version uses the fact that every palindrome has a center. For each index I expand around it with two pointers, both for the odd and even case, and update the best answer.

That shows that you:

  • understood the structure of the problem
  • chose the simpler approach before the heavier one
  • explain the optimization as a reason, not just a result

When this pattern shows up again

This pattern comes back when you need to:

  • compare mirrored characters around a point
  • work with contiguous string regions
  • prefer a structural solution over heavier dynamic programming

The point is not memorizing “expand from center.”

The point is noticing when the center is the most useful property.

Next challenge Word Break Previous challenge Generate Parentheses

Related challenges

Related articles