Adobe Software Engineer 2 Interview Experience
A few months ago, I got the opportunity to interview with Adobe for an SDE-2 role. I had been meaning to write about the experience for a while, but somehow kept putting it off. So, I’m finally sharing it now while I still remember the interview rounds, the questions, and, more importantly, the areas where I struggled.
I think those moments are often more useful to share than just saying, “I cleared the interview.” They give a more realistic picture of what to expect and what you can learn from the experience.
| Detail | Info |
|---|---|
| Role | SDE-2 |
| Experience level | Mid-level (2-4 YOE range) |
| Total rounds | 4 |
| Coding problems | 5, plus 1 pure LLD round |
| Total duration | ~4.5 hours across all rounds |
| Mode | Remote (all rounds) |
| Language used | Java (C++ theory tested verbally in Round 3) |
| Difficulty | Medium overall, a few follow-ups pushed into hard territory |
| Result | Selected |
Alright, let's get into the actual rounds.
Round 1: DSA Round (60 mins, Remote, 2 Problems)
Two coding problems, back to back, no small talk really. Straight into it.
Problem 1: Nested List Weight Sum
Ask: You get a nested list , some elements are plain integers, some are lists inside lists. Each integer's weight equals how deep it sits (outer list = depth 1). Return the total sum, where each integer counts multiple times based on its depth.
The second I read it, my brain went "this is just a tree, right?" and yeah, that instinct held up. A nested list is basically a tree with integers as leaves. So I did a DFS, dragging the current depth along as I go deeper.
public int depthSum(List<NestedInteger> nestedList) {
return dfs(nestedList, 1);
}
private int dfs(List<NestedInteger> list, int depth) {
int sum = 0;
for (NestedInteger item : list) {
if (item.isInteger()) {
sum += item.getInteger() * depth;
} else {
sum += dfs(item.getList(), depth + 1);
}
}
return sum;
}
Time complexity: O(n), where n is the total number of integers across all nested lists , every integer gets visited exactly once. Space complexity: O(d) for the recursion stack, where d is the maximum depth of nesting.
This one went smoothly, no real hiccups. The interviewer did ask one follow-up though: "What if the nesting was extremely deep, like thousands of levels , would recursion still be fine?" I said recursion could hit a stack overflow at that depth and that I'd switch to an iterative approach using an explicit stack instead of relying on the call stack. He seemed satisfied with that and we moved on , no need to actually code the iterative version.
Problem 2: Valid Palindrome III
Ask: Given a string and a number k, figure out if you can turn the string into a palindrome by deleting at most k characters.
This is the one that actually got me. My first move was to treat it like the regular "valid palindrome" two-pointer check , like, move pointers from both ends and if characters don't match, try skipping one side or the other. I started explaining this out loud.
The interviewer stopped me and asked: "Okay and what's the time complexity of that approach if you keep branching on every mismatch?"
That's the exact moment I realized I'd messed up. I paused, did the math in my head and admitted , "actually, this branches into two options every mismatch, so worst case this is exponential." He didn't say anything, just kind of waited to see what I'd do next, which honestly felt more nerve wracking than if he'd just told me the answer.
I sat with it for a bit and then thought , okay, "minimum deletions to make a palindrome" smells like a DP problem, similar to longest common subsequence style problems. I connected it to Longest Palindromic Subsequence , find the longest subsequence of the string that's already a palindrome and the leftover characters (string length minus that) are exactly what you'd need to remove.
public boolean isValidPalindrome(String s, int k) {
int n = s.length();
int[][] dp = new int[n][n];
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1;
for (int j = i + 1; j < n; j++) {
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
int longestPalinSubseq = dp[0][n - 1];
int removalsNeeded = n - longestPalinSubseq;
return removalsNeeded <= k;
}
Time complexity: O(n²), since we fill an n×n DP table. Space complexity: O(n²) for the table, though it can be optimized to O(n) if you only keep the last two rows , I mentioned this optimization out loud but we didn't code it since he seemed satisfied with the base solution.
How I actually caught the mistake: it wasn't a sudden lightbulb , it was the interviewer's complexity question that forced me to slow down and actually calculate it instead of assuming my first idea was fine. Once I said the word "exponential" out loud, I sort of embarrassed myself into fixing it. Lesson I took from this , the moment your own solution sounds "branchy" with no memoization in sight, stop and ask yourself if this is secretly a DP problem before you keep going.
Key Takeaways from Round 1
- A "nested" data structure is almost always a tree in disguise , DFS with a state variable (like depth) is usually the cleanest fix.
- "Minimum deletions/insertions to make X a palindrome" is a strong signal for a DP problem, specifically tied to Longest Palindromic Subsequence , don't try to force a two-pointer greedy fix onto it.
- If an interviewer asks about complexity mid-explanation, treat it as a signal, not just a question , it usually means your current approach has a real problem.
Round 2: LLD Round , Design a Chess Game (60 mins, Remote, No Coding)
Zero coding this round, just pure design talk and honestly it was kind of fun once I got into the flow of it.
The prompt was open ended: "design chess." That's genuinely all he said at first. So I asked a few things back , did he want full rules like castling, en passant, check/checkmate, or just the core skeleton with movement and turns? We agreed to start with the skeleton and go deeper if time allowed.
I sketched out the main pieces (pun intended):
- Board , the 8x8 grid, knows what's sitting where
- Piece (abstract) , with
Pawn,Knight,Bishop,Rook,Queen,Kingeach handling their own movement rules - Player , color, name, list of active pieces
- GameManager , runs the game loop, tracks turns, validates moves
abstract class Piece {
protected String color;
protected Position position;
abstract boolean isValidMove(Position from, Position to, Board board);
}
class Knight extends Piece {
@Override
boolean isValidMove(Position from, Position to, Board board) {
int rowDiff = Math.abs(from.row - to.row);
int colDiff = Math.abs(from.col - to.col);
return (rowDiff == 2 && colDiff == 1) || (rowDiff == 1 && colDiff == 2);
}
}
class Board {
private Piece[][] grid;
Piece getPieceAt(Position pos) {
return grid[pos.row][pos.col];
}
void movePiece(Position from, Position to) {
grid[to.row][to.col] = grid[from.row][from.col];
grid[from.row][from.col] = null;
}
}
class GameManager {
Board board;
Player currentPlayer;
boolean makeMove(Position from, Position to) {
Piece piece = board.getPieceAt(from);
if (piece == null || !piece.color.equals(currentPlayer.color)) {
return false;
}
if (!piece.isValidMove(from, to, board)) {
return false;
}
board.movePiece(from, to);
switchTurn();
return true;
}
private void switchTurn() {
// swap currentPlayer to the other side
}
}
I made Piece abstract on purpose, so each piece owns its own movement logic , adding a new piece type or a weird custom variant later doesn't touch anything else. I said this out loud specifically, since design rounds are really about showing you thought about "what happens when requirements change," not just solving today's version of the problem.
Then came the part where I got stuck. He asked: "How would you detect check or checkmate?" I gave a fairly hand-wavy answer, something like "we'd check if the king is under attack after a move." He followed up with, "Under attack by what and how do you compute that without checking every single piece every single time?"
That's when I realized my answer was too shallow , I hadn't actually thought about the cost of checking "is this square attacked." I sort of stumbled for a few seconds, then worked through it live: simulate the move, then loop through the opponent's pieces and check if any of their valid moves land on the king's square and if the move puts your own king in check, disallow it. He nodded and said "yeah, that works, just wanted to see you get there," which told me he already knew I hadn't fully thought it through and was giving me room to work it out rather than just moving on.
Looking back: I should've flagged the complexity concern myself before he asked, instead of waiting to get caught out. If you're doing a design round, try to preemptively poke holes in your own answer , it makes you look sharper than waiting for the interviewer to find the hole for you.
Key Takeaways from Round 2
- Making
Pieceabstract with per-piece movement logic is the single most important design decision in a Chess LLD , call it out explicitly, don't just write it silently.- Always think through the cost of "is this square/state under attack" style checks before you're asked , naive checking-every-piece approaches are the first thing interviewers probe.
- It's fine to work through an answer live and imperfectly. What the interviewer is often testing is whether you can get there at all, not whether you have it memorized.
Round 3: The Long One , Project Talk + C++ Grill + 2 Problems (90 mins, Remote)
This round was a marathon. Ninety minutes of project talk, rapid fire C++, code reading and two coding problems.
Project Discussion
Started with him asking about the most critical project I've worked on , the actual technical decisions, not just "what did it do." I kept my answers specific, mentioning real numbers and real tradeoffs instead of staying vague.
C++ Rapid Fire
Then a burst of quick-fire theory:
- Null pointer vs dangling pointer
- What a void pointer actually is and why you need to cast it before use
- Stack vs heap memory and who's responsible for cleanup
- Smart pointers ,
unique_ptrvsshared_ptrand how they save you from manualdeletecalls
This part moved fast enough that there wasn't much room to think , it really was testing whether this stuff was second nature or not.
Snippets and Debugging
He showed me a couple of small C++ snippets and asked me to say what they'd print. One had pointer arithmetic mixed with a pre-increment and I got it wrong on the first pass , said the wrong value out loud immediately, too quickly, without actually tracing it.
He gave me a small hint: "Walk through it line by line instead of guessing , what does the pointer actually point to before the increment happens?" That hint was basically him telling me to slow down. Once I actually traced it step by step instead of pattern-matching from memory, I caught my own mistake and corrected the answer. I remember feeling a bit embarrassed about it in the moment, but he didn't seem to mind , he just wanted to see if I could self-correct once nudged, which I think matters more than getting it right instantly.
Problem 1: Better Compression of String
Ask: Given a string like "a2b1c1a3" , a character followed by digits, possibly with the same character showing up more than once , combine the counts for each character and output the result in alphabetical order.
I used a 26-length array to keep running totals while parsing through the string, since we're dealing with lowercase letters only.
public String betterCompression(String s) {
int[] counts = new int[26];
int i = 0;
while (i < s.length()) {
char ch = s.charAt(i);
i++;
int num = 0;
while (i < s.length() && Character.isDigit(s.charAt(i))) {
num = num * 10 + (s.charAt(i) - '0');
i++;
}
counts[ch - 'a'] += num;
}
StringBuilder result = new StringBuilder();
for (int j = 0; j < 26; j++) {
if (counts[j] > 0) {
result.append((char) ('a' + j)).append(counts[j]);
}
}
return result.toString();
}
Follow-up he threw in: "What if the count could be zero, like a0? Does your code handle that correctly?" I traced through my own code and confirmed that yes, since I'm just adding zero to the running count, it naturally works without extra handling , I didn't need to change anything, just had to prove it to him with an example.
Time complexity: O(n), a single pass through the string, plus O(26) at the end to build the result, which is effectively constant. Space complexity: O(1) extra space, since the counts array is a fixed size of 26 regardless of input size.
Problem 2: Compare Version Numbers
Ask: Compare two version strings like "1.01" and "1.001" and say which is bigger, accounting for leading zeros not mattering.
public int compareVersion(String version1, String version2) {
String[] parts1 = version1.split("\\.");
String[] parts2 = version2.split("\\.");
int maxLength = Math.max(parts1.length, parts2.length);
for (int i = 0; i < maxLength; i++) {
int num1 = i < parts1.length ? Integer.parseInt(parts1[i]) : 0;
int num2 = i < parts2.length ? Integer.parseInt(parts2[i]) : 0;
if (num1 != num2) {
return num1 > num2 ? 1 : -1;
}
}
return 0;
}
He asked what happens with something like "1..1" (a double dot) or a trailing dot. I said I'd add input validation before splitting and that as written, Integer.parseInt on an empty string would actually throw an exception , I flagged this myself rather than waiting for him to catch it, which felt like a small redemption after fumbling the pointer snippet earlier.
Time complexity: O(m + n), where m and n are the lengths of the two version strings , we split and scan each once. Space complexity: O(m + n) for storing the split arrays.
Key Takeaways from Round 3
- C++ fundamentals (pointers, memory management, smart pointers) get tested even in Java-heavy interviews if your resume mentions C++ , don't skip revising them just because you code in Java day to day.
- When reading code snippets out loud, trace step by step instead of pattern-matching from memory. That's exactly where I slipped on the pointer arithmetic question.
- For string/parsing problems, always mention the zero-count or malformed-input edge case yourself before the interviewer asks , it shows you're already thinking defensively about your own code.
Round 4: Behavioral + a Real-World Caching Problem (60 mins, Remote, 1 Problem)
Behavioral
Started with a question about a past project, but the framing was more "walk me through a specific moment" than "tell me what you built." I stuck to one concrete story instead of jumping between projects.
The Cache Problem, Disguised
Instead of saying "implement LRU Cache," he framed it as: "imagine your system has limited memory and needs to cache frequently used data , how would you decide what to keep and what to throw away?"
I actually like this style of question more than the direct version, because it stops you from just pattern-matching to a memorized answer. We talked through a few eviction ideas , random eviction, least frequently used, least recently used and I explained the tradeoffs of each before settling on LRU as the right fit for what he described.
class LRUCache {
class Node {
int key, value;
Node prev, next;
Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private Map<Integer, Node> map;
private int capacity;
private Node head, tail;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
head = new Node(-1, -1);
tail = new Node(-1, -1);
head.next = tail;
tail.prev = head;
}
public int get(int key) {
if (!map.containsKey(key)) return -1;
Node node = map.get(key);
remove(node);
insertAtFront(node);
return node.value;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
remove(map.get(key));
}
if (map.size() == capacity) {
Node lru = tail.prev;
remove(lru);
map.remove(lru.key);
}
Node newNode = new Node(key, value);
insertAtFront(newNode);
map.put(key, newNode);
}
private void remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void insertAtFront(Node node) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
}
HashMap for instant lookup, doubly linked list to track recency without shifting anything around , removing or inserting a node when you already have a reference to it is O(1). Every get or put moves that node to the front and whenever we're full, whatever sits at the back is the actual least recently used item and that's what gets evicted.
One follow-up here too: "What if two threads call get and put at the same time , is this thread-safe?" I said no, not as written , I'd need to either synchronize the critical sections or use a concurrent-safe structure and mentioned ConcurrentHashMap combined with proper locking around the linked list operations as a starting point, though we didn't code it out.
Time complexity: O(1) for both get and put, since HashMap lookups and doubly linked list insert/remove are both constant time when you already have a node reference. Space complexity: O(capacity), since we store at most capacity nodes in the map and the linked list at any time.
Key Takeaways from Round 4
- When a "classic" problem gets dressed up as a real-world scenario, walk through the tradeoffs of a few approaches before naming the pattern , that's what the framing is actually testing.
- Thread-safety follow-ups are common on cache/data-structure questions , have a rough answer ready (locking, concurrent collections) even if you're not asked to code it.
- For behavioral answers, one detailed, specific story beats three vague ones every time.
How the Whole Thing Felt, Looking Back
If I'm honest, this process tested way more "can you think on your feet when someone pokes a hole in your first answer" than "do you know the answer already." Almost every round had at least one moment where the interviewer pushed back on something I said and how I responded in that moment mattered more than the initial answer itself.
Things I'd Tell Past-Me
- When your solution starts sounding "branchy" or exponential, stop and ask if it's secretly a DP problem , that's exactly where I lost time on Valid Palindrome III.
- Try to catch the holes in your own design before the interviewer does. I got asked about checkmate detection cost because I hadn't thought about it myself first , next time, I'll poke at my own answer before presenting it as final.
- If you get a code-reading question wrong, don't just blurt a correction , trace through it slowly out loud. That's literally what the interviewer hinted at and it worked.
- When a "textbook" problem gets disguised as a real scenario, resist the urge to jump straight to the memorized solution. Talk through the tradeoffs first, like they actually want you to.
- Staying calm after a wrong answer is genuinely part of what's being evaluated, not just a nice-to-have. Nobody expects a perfect run.
FAQs About the Adobe SDE-2 Interview
Is the Adobe SDE-2 interview difficult?
It's medium difficulty overall, but the follow-up questions are where it gets tough. The main problems weren't the hardest LeetCode-style questions out there, but interviewers keep pushing with "what if latency mattered" or "what if this needed to be thread-safe" type questions, so the real difficulty is in how deep you can go, not just solving the base problem.
Does Adobe ask LLD (Low-Level Design) in SDE-2 interviews?
Yes. In my case, an entire round was dedicated to designing a Chess game with no coding involved , just class structures, method signatures and reasoning about extensibility. Expect at least one round focused purely on design if you're interviewing for SDE-2 or above.
What coding questions does Adobe ask for SDE-2?
Based on my experience, expect a mix of medium-level DSA (arrays, strings, recursion, DP) and practical, real-world-framed problems like caching (LRU Cache) rather than always naming the pattern directly. Topics like Nested List Weight Sum, Valid Palindrome III, string parsing/compression problems and version comparison came up for me , string manipulation and DP showed up more than I expected.
How many rounds are there in the Adobe SDE-2 interview process?
I went through 4 rounds total: a DSA round, an LLD round, a longer mixed round (project discussion + C++ fundamentals + coding) and a final behavioral-plus-coding round with the hiring manager. All of mine were remote.
That's the full story , four rounds, one chess board, an embarrassing pointer mistake and a cache that thankfully didn't evict me from the process. Hope this helps if you've got something similar coming up.
