← J.P. Morgan Interview Insights

J.P. Morgan·Software Engineer·Technical Phone Screen·Intermediate

Intermediate
Jul 2026

Summary

J.P. Morgan software engineer interview that went deep on dynamic programming, specifically the longest palindromic subsequence problem. They wanted both memoized and bottom-up solutions plus a reconstruction step, which is more than most companies bother asking for.

Questions Asked (3)

Q1

Given a string, write a function that returns the length of the longest subsequence that reads the same forwards and backwards. Implement it two ways: top-down with memoization and bottom-up with a table. Then analyze time and space complexity and explain how you'd reduce space usage.

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

I started with the recursive approach and got it working pretty fast, but then they asked me to also write the bottom-up version on the spot and I fumbled the table initialization for a bit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by defining the problem as finding the longest palindromic subsequence (LPS) using dynamic programming. Explain the recurrence: if the first and last characters match, LPS length is 2 + LPS of the inner substring; otherwise, it's the maximum of LPS excluding either end. Then implement top-down with memoization and bottom-up with a table, analyze complexities, and discuss space optimization.

Pro tip: In interviews, always clarify that a subsequence doesn't require contiguous characters, unlike a substring. This shows attention to detail and prevents misunderstandings.

1. Clarify and Define

Confirm that the problem asks for the longest palindromic subsequence (not substring) and that characters can be skipped. Define the DP state: dp[i][j] = length of LPS in substring s[i..j].

2. Derive Recurrence

Write the recurrence: if s[i] == s[j], dp[i][j] = 2 + dp[i+1][j-1] (with base case for i==j being 1); else dp[i][j] = max(dp[i+1][j], dp[i][j-1]).

3. Implement Top-Down

Implement a recursive function with memoization (e.g., using a 2D array initialized to -1) to avoid recomputing overlapping subproblems. Handle base cases: i > j returns 0, i == j returns 1.

4. Implement Bottom-Up

Create a 2D table of size n x n, fill diagonals for substrings of increasing length. Initialize dp[i][i] = 1, then iterate lengths from 2 to n, filling dp[i][j] using the recurrence.

5. Analyze and Optimize

State time complexity O(n^2) and space complexity O(n^2) for both approaches. Explain that space can be reduced to O(n) by observing that each row depends only on the previous row and the current row's left value, so we can use two 1D arrays or even one array with careful updating.

Key Points to Mention

  • Difference between subsequence and substring: subsequence allows skipping characters.
  • Optimal substructure and overlapping subproblems justify DP.
  • Top-down memoization uses recursion and a cache; bottom-up builds iteratively.
  • Time complexity O(n^2) and space complexity O(n^2) for both, but bottom-up can be optimized to O(n) space.
  • Space optimization technique: use two rows (previous and current) or a single array with backward iteration.
  • Edge cases: empty string returns 0, single character returns 1.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

After finding the length, how would you reconstruct an actual valid longest palindromic subsequence? Walk through the approach.

Algorithms & Data Structures
Author's notes

This is where I started losing steam.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Explain that you can reconstruct the LPS by backtracking through the DP table used to compute the length, or by storing parent pointers during the DP. Then walk through the backtracking process step by step, showing how to build the subsequence from both ends.

Pro tip: Mention that storing parent pointers during DP computation can simplify reconstruction and avoid re-deriving choices, but it increases space complexity. Alternatively, backtracking from the DP table is more space-efficient and demonstrates deeper understanding.

1. Review DP table construction

Recall that dp[i][j] stores the length of the longest palindromic subsequence in substring s[i..j]. The recurrence is: if s[i] == s[j], dp[i][j] = dp[i+1][j-1] + 2; else dp[i][j] = max(dp[i+1][j], dp[i][j-1]).

2. Initialize pointers and result

Start with i = 0, j = n-1, and an empty list or deque to store characters. We'll build the subsequence from the outside in.

3. Backtrack through the DP table

While i <= j: if s[i] == s[j], add s[i] to the front and back of the result (or store for later), then i++, j--. Else if dp[i+1][j] >= dp[i][j-1], move i++ (skip s[i]); otherwise move j-- (skip s[j]).

4. Handle the middle character

If i == j after the loop, add s[i] to the middle of the result. If i > j, no middle character is needed.

5. Construct and return the subsequence

Combine the left and right parts (and middle if any) to form the final longest palindromic subsequence string.

Key Points to Mention

  • DP recurrence for LPS length: dp[i][j] = dp[i+1][j-1] + 2 if s[i]==s[j], else max(dp[i+1][j], dp[i][j-1]).
  • Backtracking from dp[0][n-1] to reconstruct the subsequence by comparing characters and DP values.
  • Time complexity O(n^2) for DP and O(n) for reconstruction; space complexity O(n^2) for DP table.
  • Alternative: store parent pointers during DP to directly reconstruct without backtracking logic.
  • Handling edge cases: empty string, single character, all characters same.
  • Building the palindrome from both ends ensures correct order and symmetry.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

If you had to run this across many test cases efficiently, what would you change about your approach?

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Short answer: precompute and cache.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Acknowledge the current solution's limitations for scale, then systematically propose optimizations across algorithm, data structures, and system design. Emphasize trade-offs between time, space, and complexity, and validate improvements with testing and metrics.

Pro tip: Quantify the impact of each change (e.g., 'reduces time complexity from O(n^2) to O(n log n)') and mention how you'd validate with benchmarks and edge cases. This shows you think like a production engineer, not just a coder.

1. Identify bottlenecks

Analyze the current approach to find performance bottlenecks, such as high time complexity, redundant computations, or memory issues. Consider profiling or theoretical analysis.

2. Propose algorithmic improvements

Suggest better algorithms or data structures (e.g., hash maps, heaps, dynamic programming) to reduce complexity. Explain how they address the bottlenecks.

3. Consider system-level optimizations

Discuss parallelization, caching, batching, or distributed processing if applicable. Mention how these scale with many test cases.

4. Evaluate trade-offs

Compare the proposed changes in terms of time, space, readability, and maintainability. Acknowledge any new complexities introduced.

5. Validate and iterate

Describe how you would test the optimized solution with large inputs, measure performance, and ensure correctness. Mention continuous improvement.

Key Points to Mention

  • Time and space complexity analysis (Big O notation)
  • Appropriate data structures (e.g., hash tables, trees, graphs)
  • Algorithm design paradigms (e.g., divide and conquer, dynamic programming, greedy)
  • Parallelization and concurrency (e.g., multi-threading, MapReduce)
  • Caching and memoization to avoid redundant work
  • Testing with large datasets and performance benchmarking

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.