HARD Lab · Strings · 19

5. Longest Palindromic Substring

Every palindrome has a center — either one character (odd length) or a gap between two equal characters (even length). Expand outward from each of the 2n − 1 possible centers and track the widest match seen, yielding an O(n²) solution with O(1) extra space.

MediumExpand Around CenterTypeScript

PROBLEM What we're solving

Given string s, return the longest contiguous substring that reads the same forwards and backwards. For s="babad" the answer is "bab" (or "aba" — both length 3 are valid). For s="cbbd" the answer is "bb". Palindromes are everywhere: DNA reverse complements, string hashing, and a dozen interview follow-ons all hinge on this primitive.

KEY IDEA Every palindrome radiates outward from a center

Insight → a palindrome is symmetric around its midpoint. If you stand at the center and walk left and right simultaneously, you keep reading matching characters until the palindrome ends. There are exactly 2n − 1 possible centers in a string of length n — one per character (odd-length palindromes) plus one per gap between adjacent characters (even-length palindromes). Expand from every center, remember the widest window seen: done.

RECIPE Expand from every center

  • 0 · Init best. Keep bestL = bestR = 0 (the single first character is the trivial palindrome). This avoids a special-case for empty or length-1 input.
  • 1 · Define expand(l, r). While l >= 0, r < n, and s[l] === s[r]: update best if this window is wider, then decrement l and increment r. O(n) in the worst case per center.
  • 2 · Loop over every index i. Call expand(i, i) for the odd-length center (a single character always palindromes) and expand(i, i+1)for the even-length center (the gap; the while loop won't enter if s[i] !== s[i+1], so it's safe).
  • 3 · Return. s.slice(bestL, bestR + 1).
Classic confusion → forgetting the even-length centers. The gap between "b" and "b" in "cbbd" must be seeded with expand(i, i+1), not expand(i, i). Many candidates code only the odd case and fail on inputs like "bb" or "abbc".

COST Complexity & alternatives

Brute force: all substrings
O(n³)
O(n²) substrings × O(n) palindrome check each.
Expand around center
O(n²) / O(1)
2n−1 centers × O(n) expansion; no extra space.

Optimal: Manacher's algorithm

Manacher's uses previously computed palindrome radii to skip redundant expansions, achieving O(n)time. It's interview-overkill for most settings — learn the idea, but code expand-around-center in practice.

Pattern transfer → expand-around-center recurs in Palindromic Substrings (LC 647, count all), Palindrome Pairs (LC 336), and Palindrome Partitioning(LC 131). The "two pointers moving outward from a fixed pivot" shape also appears in Trapping Rain Water and two-pointer problems where the invariant tightens toward a center.

RUN IT Expand around every center, track the longest palindrome

step 0 / 29
STARTInput: "babad". We will try every center — 5 odd + 4 even — and expand outward to find the longest palindromic substring.
s =b0a1b2a3d4
State
"b"
best
0
bestL
0
bestR
current centerexpanding windowcurrent best palindromemismatch / boundary
slowfast

TYPESCRIPT The solution, annotated

longestPalindrome.ts
function longestPalindrome(s: string): string {
  let bestL = 0, bestR = 0;

  function expand(l: number, r: number): void {
    while (l >= 0 && r < s.length && s[l] === s[r]) {
      if (r - l > bestR - bestL) { bestL = l; bestR = r; }
      l--;
      r++;
    }
  }

  for (let i = 0; i < s.length; i++) {
    expand(i, i);     // odd-length centers
    expand(i, i + 1); // even-length centers (the gap between i and i+1)
  }

  return s.slice(bestL, bestR + 1);
}

Reading it block by block

Lines 2 — track best indices. Store bestL and bestR as character indices rather than the substring itself to avoid repeated string allocation during expansion.
Lines 4–9 — the expand helper. Takes the initial left and right pointers; while both are in bounds and characters match, it checks if this window beats the current best and then moves outward. The condition r - l > bestR - bestL compares spans so no Math.abs needed.
Lines 11–14 — the main loop. At every index i, fire two seeds: expand(i, i) for odd-length (single-char center) and expand(i, i+1) for even-length (character-gap center). The even call safely no-ops if s[i] !== s[i+1].
Line 16 — return the slice. s.slice(bestL, bestR + 1) is O(k) where k is the answer length; this is unavoidable since we must materialize the string.
Complexity → O(n²) time — 2n−1 centers, each expanding at most O(n). O(1) extra space — only two index variables track the best. Manacher's algorithm can achieve O(n) but is rarely expected.

INTERVIEWFollow-ups they'll ask

  • "Can you do it in O(n)?"Yes — Manacher's algorithm. It reuses the palindrome radius of a previously computed center to avoid redundant comparisons. Worth knowing the idea; rarely worth coding from scratch in an interview.
  • "Return all palindromic substrings (LC 647)?" Same expand loop; instead of tracking the best, increment a counter every time the while condition holds.
  • "What about case-insensitive or with spaces?" Normalize (lowercase, strip non-alphanumeric) before running the algorithm — the core logic is unchanged.
  • "What if you need the count of distinct palindromic substrings?" Harder — requires suffix arrays or Eertree (palindrome tree). Mention it to show range.
  • "Brute force first?" O(n³): iterate all O(n²) substrings, check each in O(n). Expand-around-center improves to O(n²) by centering the check rather than re-examining every pair.

MNEMONIC The one-liner

"Stand at the center, walk outward while the mirrors match — 2n−1 centers covers every case."

TRIGGERS When you see ___ → reach for ___

"longest palindromic…" or "find palindrome in string"expand around center
even-length palindrome case (e.g. "bb", "abba")seed with expand(i, i+1)
O(1) space string symmetry checktwo outward pointers from a pivot
count/find all palindromic substringssame expand loop, add a counter

SKELETON The reusable shape

skeleton.ts
function longestPalindrome(s: string): string {
  let bestL = 0, bestR = 0;

  function expand(l: number, r: number): void {
    while (l >= 0 && r < s.length && s[l] === s[r]) {
      if (r - l > bestR - bestL) { bestL = l; bestR = r; }
      l--; r++;
    }
  }

  for (let i = 0; i < s.length; i++) {
    expand(i, i);       // odd
    expand(i, i + 1);   // even
  }

  return s.slice(bestL, bestR + 1);
}

FLASHCARDS Tap to flip

How many possible centers are there in a string of length n?
2n − 1: n odd-length centers (each character) + n−1 even-length centers (each gap between adjacent characters).
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 7
QUESTION 01
What is the time complexity of the expand-around-center approach?
QUESTION 02
For s="cbbd", what is the answer and which center produces it?
QUESTION 03
Why must we call both expand(i, i) and expand(i, i+1) at each index?
QUESTION 04
What does expand(i, i+1) do when s[i] !== s[i+1]?
QUESTION 05
What is the extra space complexity of expand-around-center?
QUESTION 06
Trace s="racecar". Which center yields the longest palindrome?
QUESTION 07
Which follow-on problem is solved by the same expand loop but counting instead of tracking the best?