LogIn
I don't have account.

My Microsoft SDE-2 Interview Experience (2026) : Paused, Closed and Then Reopened Two Months Later

Sameer Kulkarni
271 Views

#microsoft

#sde-2

#interview-experience

#backend-interview-experience

This one didn't go in a straight line and I think that's exactly why it's worth writing down. I interviewed for an SDE-2 role on the Windows team as part of a hiring drive. Three rounds in, everything just... stopped. Not rejected. Not moving forward. Paused. Then the role got closed entirely. Then, almost two months later, a completely different team reached out because they'd read my old feedback and wanted to pick up where it left off.

I want to walk through all five rounds, including the gap in the middle, because the gap taught me more about how these processes actually work than any single round did.

Verdict: Selected.

Detail Info
Company Microsoft
Role SDE-2, originally Windows team
Total rounds 5 (across two separate hiring attempts)
Overall Difficulty ⭐⭐⭐☆☆ (3.5/5)
Unusual twist Process paused after Round 3, role closed, reopened 2 months later by a different team
Result Selected

Round 1 : DSA

Two coding questions, plus some back-and-forth on core DSA concepts along the way.

Question 1: A Variation of Longest Repeating Character Replacement

The problem: given a string and an integer k, find the length of the longest substring you can turn into all-one-character by changing at most k characters.

I talked through the brute-force approach first check every substring, count the most frequent character inside it and see if (length - mostFrequentCount) <= k. That's straightforward but slow, O(n²) substrings with counting work inside each one.

static int bruteForce(String s, int k) {
    int n = s.length();
    int best = 0;
    for (int i = 0; i < n; i++) {
        int[] count = new int[26];
        int maxFreq = 0;
        for (int j = i; j < n; j++) {
            count[s.charAt(j) - 'A']++;
            maxFreq = Math.max(maxFreq, count[s.charAt(j) - 'A']);
            int windowLen = j - i + 1;
            if (windowLen - maxFreq <= k) best = Math.max(best, windowLen);
        }
    }
    return best;
}

The optimal approach is a sliding window and it has a genuinely counterintuitive trick baked into it that I made sure to call out explicitly rather than gloss over: the window never needs to shrink, only slide. My first instinct, like most people's, was to shrink the window back down the moment it became invalid, carefully decrementing the character count for whatever falls off the left edge. That's not wrong, exactly, but it's easy to get subtly wrong in a way that still looks reasonable:

// A tempting mistake: advance the window past an invalid state,
// but forget to decrement the count for the character sliding out on the left.
static int buggyForgetsToDecrement(String s, int k) {
    int[] count = new int[26];
    int left = 0, maxFreq = 0, best = 0;
    for (int right = 0; right < s.length(); right++) {
        count[s.charAt(right) - 'A']++;
        maxFreq = Math.max(maxFreq, count[s.charAt(right) - 'A']);
        while ((right - left + 1) - maxFreq > k) {
            left++; // BUG: count[] never gets decremented here
        }
        best = Math.max(best, right - left + 1);
    }
    return best;
}

I actually walked through this exact bug with the interviewer as part of "discussing both approaches" on "AABABBA" with k = 1, this buggy version returns 5, but the real answer is 4. Once a character's count silently stays too high forever, maxFreq looks artificially inflated and the window is allowed to grow past what's actually valid.

The correct optimal version leans into the trick fully maxFreq is allowed to become a stale over-estimate after a slide and that's fine, because a stale, too-high maxFreq can only ever prevent an unnecessary shrink; it can never let the window grow into a genuinely invalid, longer state than it should:

static int optimal(String s, int k) {
    int[] count = new int[26];
    int left = 0, maxFreq = 0, best = 0;
    for (int right = 0; right < s.length(); right++) {
        count[s.charAt(right) - 'A']++;
        maxFreq = Math.max(maxFreq, count[s.charAt(right) - 'A']);
        if ((right - left + 1) - maxFreq > k) {
            count[s.charAt(left) - 'A']--;
            left++;
        }
        best = Math.max(best, right - left + 1);
    }
    return best;
}

I cross-checked this against the brute-force version on 2,000 randomized strings afterward and every result matched the O(n) sliding window and the O(n²) brute force always agreed.

Question 2: A Medium Tree Traversal Problem

I don't remember the exact wording of this one anymore either, which I'll be upfront about rather than pretend otherwise. It was in the same general family as zigzag / spiral level-order traversal, so here's a representative version of that problem, solved properly, since the specific phrasing didn't stick the way the first question did.

The problem: traverse a binary tree level by level, but alternate direction each level left-to-right, then right-to-left, then left-to-right again.

The natural approach is a standard BFS with a queue, collecting one level at a time and reversing every other level before adding it to the result. The trap here is easy to fall into under pressure: reversing every level instead of only the alternating ones.

static List<List<Integer>> correct(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    if (root == null) return result;
    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    boolean leftToRight = true;
    while (!queue.isEmpty()) {
        int size = queue.size();
        List<Integer> level = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
        if (!leftToRight) Collections.reverse(level);
        result.add(level);
        leftToRight = !leftToRight;
    }
    return result;
}

On the tree 3 -> (9, 20), with 20 -> (15, 7): the correct output is [[3], [20, 9], [15, 7]]. A version that reverses every level unconditionally instead gives [[3], [20, 9], [7, 15]] that last level is wrong, since it should have stayed in left-to-right order.

Follow-Up Questions I Got and What I Said

  • "Why doesn't the sliding window need to shrink back down properly?" Because the only thing that matters is whether we've ever seen a window of a certain length that was valid a stale maxFreq can only make us miss shrinking when we technically could, never let an invalid window get counted as the answer, since the window length itself only grows when a genuinely larger valid window is found.

  • "What's the time complexity difference between the brute force and the optimal approach?" Brute force is O(n²) (or worse, depending on how you recompute the max character count). The sliding window is O(n), since both pointers only ever move forward, each at most n times total.

  • "For the tree problem, how would you do it without a queue, just recursion?" I said you can do it with DFS by tracking the current depth and pre-allocating a list per depth, appending to the front or back of that depth's list depending on whether the depth is even or odd same result, different mechanics and arguably a bit more fiddly to get right than the BFS version.

Round 2 : LLD (AI-Assisted)

The problem: design a notification system for Windows, where different applications can generate and display notifications.

This round had a twist I hadn't seen before: the interviewer explicitly told me to use AI during the round. My first reaction was mild confusion about what was actually being evaluated was it still my design or was it now a test of how well I could operate a tool?

It turned out to be the second one, but not in the way I expected. There were a couple of points in the design where I genuinely wasn't sure of the best approach specifically around how to handle per-application rate limiting so one noisy app couldn't flood the notification tray for every other app. I talked through my own thinking with the interviewer first, out loud, before touching any AI tool at all. Only after that did I turn to AI to explore alternatives I hadn't considered.

What the interviewer actually watched closely was how I framed my prompts, how I refined them when the first response wasn't useful and most importantly how critically I evaluated what came back instead of just accepting it. At one point the AI suggested an approach I didn't fully agree with and I said so directly, along with my reasoning for preferring a different structure. That pushback seemed to matter more than any single AI suggestion did.

My Mistake Here: Reaching for AI a Little Too Early on One Sub-Problem

If I'm honest, there was one specific piece how to model notification grouping so multiple notifications from the same app collapse into a single expandable entry where I turned to AI slightly before I'd fully worked through my own instinct. The interviewer didn't call this out directly, but he did ask a pointed question right afterward: "What would you have proposed here on your own, before seeing that?" That was enough of a nudge for me to backtrack and actually answer it myself first, which I probably should have done before opening the AI tool at all.

The Core Design, in Brief

Notification (source app, priority, title/body, timestamp, grouping key), App (registered publisher with its own rate-limit budget) and a NotificationManager that accepts incoming notifications, applies per-app rate limiting, groups by the grouping key and hands off to a Renderer responsible for actually displaying things in the tray. Keeping rate-limiting and grouping as their own responsibilities, separate from rendering, was the part of the design that held up best under follow-up questions it meant a policy change (say, a stricter rate limit during "Do Not Disturb" hours) didn't require touching anything about how notifications are actually drawn on screen.

Follow-Up Questions I Got and What I Said

  • "How would you prevent one misbehaving app from spamming the user with notifications?" A token-bucket style rate limiter per app, refilling over time, so a burst is allowed but a sustained flood gets throttled refused notifications either get silently dropped or grouped into a single "N more from this app" summary rather than shown individually.

  • "How would 'Do Not Disturb' mode fit into this design?" As a global policy check the NotificationManager consults before handing anything to the Renderer during Do Not Disturb, notifications still get accepted and stored (so nothing is lost), just not immediately rendered and a batched summary can surface once the mode turns off.

  • "If you used AI again for a different part of this design, what would you do differently based on how this round went?" I said I'd commit to my own first-pass answer out loud before ever opening the tool, every time, specifically because of the moment I got caught slightly under-prepared on the grouping question using AI to pressure-test an idea I already have is a very different thing from using it to generate the idea in the first place.

This was my first AI-assisted LLD round. Overall it went well, but that one moment stuck with me as the thing I'd tighten up next time.

Round 3 : HLD

The problem: proximity search given a user's location, find restaurants within a 10 km radius for a food delivery use case.

We went deep on this one. The conversation naturally expanded from "how do you find nearby restaurants" into questions about how the index actually scales and toward the end I was asked to code part of the approach directly.

The core idea: don't compute distance against every restaurant in the database for every query. Bucket restaurants into a coarse grid (roughly the same concept behind geohashing, Uber's H3 or a quadtree different implementations of the same underlying idea), so a query only has to scan the user's cell and its immediate neighbors, then compute exact distance only against that much smaller candidate set.

static double haversineKm(double lat1, double lng1, double lat2, double lng2) {
    double dLat = Math.toRadians(lat2 - lat1);
    double dLng = Math.toRadians(lng2 - lng1);
    double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
            + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
            * Math.sin(dLng / 2) * Math.sin(dLng / 2);
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return 6371.0 * c; // Earth's radius in km
}

I checked this formula against a known real-world distance before trusting it in the interview-style walkthrough here New York to Los Angeles comes out to about 3,936 km, matching the commonly cited great-circle distance between the two cities almost exactly.

The grid index itself buckets each restaurant by a coarse lat/lng cell and a radius query scans the user's cell plus a ring of neighboring cells (padded enough to catch restaurants just across a cell boundary), filtering the small candidate set down with the real Haversine distance:

List<Restaurant> findWithinRadius(double lat, double lng, double radiusKm) {
    List<Restaurant> candidates = new ArrayList<>();
    int span = (int) Math.ceil(radiusKm / cellSizeKm) + 1;
    for (int dLat = -span; dLat <= span; dLat++) {
        for (int dLng = -span; dLng <= span; dLng++) {
            for (Restaurant r : cells.getOrDefault(neighborCellKey(dLat, dLng), List.of())) {
                if (haversineKm(lat, lng, r.lat(), r.lng()) <= radiusKm) {
                    candidates.add(r);
                }
            }
        }
    }
    return candidates;
}

On a small test set one restaurant about 1.5 km away, one about 6 km away and one about 78 km away, all queried with a 10 km radius the search correctly returned only the first two and excluded the far one.

Follow-Up Questions I Got and What I Said

  • "What happens as the number of restaurants grows into the tens of millions?" I said the grid (or geohash) approach scales horizontally naturally, since each cell is independent you can shard by cell range across multiple database nodes or search index shards without restaurants in one city needing to know anything about restaurants in another.

  • "How would you handle a restaurant that's very close to a cell boundary?" This is exactly why the search scans a ring of neighboring cells, not just the exact cell the user is standing in a restaurant just across the boundary from the user's cell would otherwise be missed even though it's genuinely close by.

  • "Would you use a SQL database with geospatial extensions or something like Elasticsearch?" I said either can work PostGIS-style extensions give you geospatial indexing inside a relational model if you also need strong transactional guarantees elsewhere, while Elasticsearch's geo-distance queries are a natural fit if search and filtering (cuisine type, rating, open-now) matter as much as pure distance. I'd let the rest of the system's requirements decide, rather than picking one reflexively.

  • "How would results be ranked once you have the candidates within radius?" Distance alone is rarely the full ranking I mentioned combining distance with rating, estimated delivery time and promoted/sponsored placement, similar to how most real delivery apps blend several signals rather than sorting by raw distance.

The Long Pause

After Round 3, the process just stopped. No rejection, no next steps, just silence. I followed up with HR more than once. At one point a fourth round actually got scheduled and then cancelled because the hiring manager wasn't available. A few more follow-ups after that and I was told the position itself had been closed. The HR contact was kind about it and mentioned that if the team hired for this role again in the future, they'd reach back out.

I treated that as the end of it. I wasn't holding my breath.

Almost two months later, a different HR contact reached out about a completely different team. They'd gone through my feedback from the earlier rounds and wanted to continue the process based on that. I hadn't reapplied, hadn't followed up asking for this it came entirely from them acting on the earlier feedback.

Round 4 : OS and C++

This round was built almost entirely around my resume and past experience, but it went deep into operating systems and C++ fast.

The main question: how would you implement the Linux cp command?

My first answer was the honest, simple one open the source file, open (or create) the destination file, read in chunks, write those chunks out, close both. But the conversation didn't stop there and this is where it got genuinely challenging for me.

My Mistake: Assuming a Single write() Call Writes Everything You Asked For

I initially described the write step as one call per chunk read 8KB, write that same 8KB, move on. The interviewer asked, "Is a single write() call guaranteed to write the full number of bytes you passed it?" I said yes, without thinking hard enough about it and that was wrong. A write() call can return having written fewer bytes than requested a short write particularly relevant on pipes or when interrupted by a signal and just trusting its return value without checking is a real, if subtle, way to silently corrupt a copy on certain inputs.

The fix is to loop on write() until every byte in the current chunk is actually confirmed written:

ssize_t bytesWrittenSoFar = 0;
while (bytesWrittenSoFar < bytesRead) {
    ssize_t n = write(dstFd, buffer + bytesWrittenSoFar, bytesRead - bytesWrittenSoFar);
    if (n < 0) { /* handle error */ break; }
    bytesWrittenSoFar += n;
}

I also talked through preserving the source file's permission bits (via fstat on the source, then passing those mode bits into the open() call for the destination) and handling the missing-source-file case explicitly rather than letting it crash. I actually built and tested a small version of this afterward to make sure the reasoning held up under a real byte-for-byte comparison, not just in conversation:

int srcFd = open(src, O_RDONLY);
struct stat srcStat;
fstat(srcFd, &srcStat);
int dstFd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, srcStat.st_mode & 0777);
// ...read/write loop above...

Copying a 500 KB random binary file through this came back byte-identical to the original (verified with cmp) and the destination file's permissions matched the source exactly (640 in, 640 out). Pointing it at a nonexistent source file also failed cleanly with a real error message instead of crashing.

Follow-Up Questions I Got and What I Said

  • "Why read in fixed-size chunks instead of reading the whole file into memory at once?" Memory. A naive full-file read works fine for small files but falls over (or at minimum wastes a huge amount of RAM) on anything large a fixed chunk size keeps memory usage constant regardless of file size.

  • "What would you need to change to support copying a directory recursively, like cp -r?" I said you'd need to detect that the source is a directory (via the same stat call, checking S_ISDIR on the mode), then create the destination directory and recurse into each entry inside it, applying the same file-copy logic at the leaves.

  • "What happens if the destination file already exists?" As written, it truncates and overwrites, matching plain cp's default behavior. I mentioned that a real implementation would probably want an -i (interactive/confirm) flag and possibly a -n (no-clobber) flag, mirroring actual cp behavior, rather than always overwriting silently.

This round was harder for me than the earlier ones OS-level detail isn't something I live in day-to-day and the write-loop question in particular caught me flat-footed. It went okay overall, but it was the round where I felt least in control of the conversation.

Round 5 : Hiring Manager Round

This was mostly a detailed discussion, not really a question-answer format. The interviewer walked through my projects, the technical decisions behind them, what I personally contributed versus what the team did, the hardest parts of each project and the actual impact once things shipped. It felt more like a peer conversation about my work than an evaluation and the interviewer was friendly the entire way through.

The next day, HR called with an offer.

Biggest Learnings

The round that went worst for me Round 4 wasn't worse because I lacked the conceptual knowledge. I knew what cp needed to do in broad strokes. It went worse because I hadn't questioned an assumption I'd been carrying for years: that write() just works, every time, for the full amount requested. The AI-assisted round taught me something related from a completely different angle using a tool to explore alternatives is fine and even expected, but it's not a substitute for committing to your own answer first. Both mistakes were really the same shape: trusting a default assumption (about a syscall, about reaching for a tool) instead of pausing to check it.

And separately from any single round: the two-month gap taught me that a paused or closed process isn't necessarily a closed door. Feedback from a round you thought was wasted can resurface somewhere you didn't expect.

Preparation Tips

For sliding window problems, specifically internalize the "the window doesn't need to shrink, just slide" trick where it applies it shows up across a whole family of substring problems and getting caught trying to carefully shrink and re-validate the window is one of the most common ways people slow themselves down or introduce a subtle bug under pressure.

If you're ever told to use AI during a round, resist the urge to reach for it as your first move on a hard sub-problem. Form your own answer out loud first, then use the tool to stress-test or extend it that ordering is what actually gets evaluated.

For proximity/geospatial system design questions, know at least one concrete indexing approach (grid bucketing, geohashing or a quadtree) well enough to explain why scanning every record directly doesn't scale and be ready to sanity-check a distance formula against a real-world example if asked to code any part of it.

For OS/C++ rounds, don't assume any single syscall does exactly what its name implies with no edge cases write() not writing everything requested is a classic example and the habit of asking "what could this call NOT guarantee" before writing the happy path is worth building deliberately.

If a process goes quiet after several rounds, keep following up, but don't treat silence as a verdict either way. Mine went from "paused" to "the role is closed" to, two months later, an entirely different team reopening it based on old feedback none of which I could have predicted or forced by anything other than staying reachable and continuing to follow up reasonably.

FAQs

How many rounds does the Microsoft SDE-2 interview loop have?

In my case, 5 total, but across two separate hiring attempts for two different teams 3 rounds (DSA, LLD, HLD) for the original Windows team role, then 2 more rounds (OS/C++, hiring manager) after a different team picked up my profile two months later.

What happens if a hiring process gets paused or the role closes?

In my experience, it isn't necessarily final. My original role closed outright and I was told to expect nothing further yet a different team reopened the process nearly two months later based on my earlier interview feedback, without me reapplying.

Does Microsoft do AI-assisted interview rounds?

At least one of my rounds was explicitly structured this way I was told to use AI during a live LLD round. The evaluation focused on how I framed and refined prompts and how critically I evaluated the output, not on whether the AI's suggestions were good on their own.

What kind of system design topics come up for a Windows-team SDE-2 role?

In my loop: a notification system (LLD) and a geospatial proximity search system (HLD). Neither was Windows-internals-specific both were general system design skills applied to a plausible product scenario.

How much OS and C++ depth is expected at the SDE-2 level?

Enough to reason about real syscall behavior, not just high-level concepts my round went from "how would you implement cp" all the way down to whether a single write() call is guaranteed to write everything you asked it to (it isn't).

Conclusion

Three rounds, a pause, a closed role, two months of silence and then two more rounds with a completely different team that read my old feedback and decided to pick up the thread themselves. I'd almost let go of the idea that anything would come of the first three rounds. If you're in the middle of a process that's gone quiet, I don't have a way to tell you it'll resolve the way mine did but it's worth remembering that quiet isn't always the same as over.

Responses (0)

Write a response

CommentHide Comments

No Comments yet.