Swiggy Interview Questions · 2026

Swiggy Interview Questions (2026): Real, With Answers

A candidate interviewing for an SDE-2 role at Swiggy described five back to back rounds: an online test on HackerEarth, a machine coding round, a data structures and algorithms round, a system design round, and a hiring manager round, all inside a single loop with no breather in between (GeeksforGeeks, Swiggy SDE-2 interview experience). That five-round shape shows up again and again in candidate reports, though the online test platform swaps between HackerRank and HackerEarth depending on the team, and frontend loops trade one DSA round for a React and JavaScript machine coding round instead.

The reported difficulty rating sits at 3.13 out of 5 on Glassdoor, with 58.3 percent of candidates describing the overall experience as positive and an average hiring process length of 24 days (Glassdoor, Swiggy Interview Questions). That's a longer loop than most food delivery peers run, largely because of the machine coding round, which candidates on Blind describe as a genuine 60 to 120 minute build, not a whiteboard sketch, followed by a code review where the interviewer pokes at your design decisions for another 30 minutes (Blind, Machine Coding Round - Swiggy).

This page covers the questions candidates actually report across seven areas: the online assessment, core DSA rounds, the machine coding round (Splitwise and parking lot systems come up repeatedly), system design for senior roles, data and product domain questions for DS and PM candidates, the hiring manager discussion, and the closing HR round. Where reports were thin for a stage, we say so rather than inventing detail.

~50Questions
4-6Rounds
24 daysAvg. Process Length
3.13/5Difficulty

Online assessment: HackerRank and HackerEarth

The first filter for most SDE roles is a timed online test, usually two to three problems in 60 to 90 minutes. Reports mention a mix of graph and dynamic programming problems for backend roles, and a lighter DSA plus JavaScript combination for frontend roles.

Easy questions

15

Read all three problems before writing a line of code. Rank them by how quickly you can picture a correct approach, not by which looks easiest to read. Spend the first 10 minutes doing that triage, then commit roughly 25 minutes to the two you're most confident about and bank a working, tested solution before touching the hardest one.

On a platform like HackerRank or HackerEarth, a solution that passes 70 percent of test cases on two problems scores higher than a solution that passes 100 percent on one and zero on the other two. If the DP problem is the one giving you trouble, get a brute force recursive solution submitted first for partial credit, then spend remaining time adding memoization rather than starting over with a bottom-up table from scratch.

In an undirected graph, a node with one visited neighbor that isn't its immediate parent is a cycle. In a directed graph that check gives false positives, because a node can point at an already-visited node that sits on a completely different path with no cycle involved.

The fix is to track two sets during DFS: nodes fully finished (all their descendants explored) and nodes currently on the active recursion stack. A cycle exists only if you reach a node that's still on the current stack, not just previously visited.

python
def has_cycle(graph, n):
  WHITE, GRAY, BLACK = 0, 1, 2
  color = [WHITE] * n

  def dfs(u):
    color[u] = GRAY
    for v in graph[u]:
      if color[v] == GRAY:
        return True
      if color[v] == WHITE and dfs(v):
        return True
    color[u] = BLACK
    return False

  return any(color[i] == WHITE and dfs(i) for i in range(n))

State the problem back in your own words and work a tiny example by hand, three or four elements, before touching a keyboard. Most candidates who freeze on an online test aren't stuck on the algorithm, they're stuck because they started coding a half-formed idea and only noticed it was wrong halfway through, with the clock still running.

Once you've traced a small example by hand and it matches your mental approach, code the brute force version first even if you already suspect a faster one exists. A working O(n^2) submission that passes some test cases beats a half-finished attempt at the optimal solution that passes none.

Edge cases that the visible examples don't exercise: empty input, a single element, all elements identical, or integer overflow on very large inputs. Visible test cases on these platforms are usually chosen to illustrate the problem statement, not to stress-test your solution.

Before submitting, mentally run your solution against an empty array, an array of size one, and the largest input size the constraints allow. That habit alone catches most of the hidden-test failures candidates report after an online round.

Walk the list once, keeping three pointers: the previous node, the current node, and a temporary pointer to the current node's next before you overwrite it. At each step, point current's next back at previous, then shift all three pointers forward by one.

python
def reverse_list(head):
  prev = None
  curr = head
  while curr:
    nxt = curr.next
    curr.next = prev
    prev = curr
    curr = nxt
  return prev

This iterative version runs in O(n) time and O(1) space. A recursive version is just as easy to write but costs O(n) stack space, which is the detail interviewers usually want you to volunteer unprompted.

A naive approach recomputes height at every node, checking balance separately, which is O(n^2) in the worst case because height gets recalculated repeatedly for the same subtrees. The clean version computes height and checks balance in a single bottom-up pass, returning a sentinel value like -1 the moment an imbalance is found anywhere below, so the whole call chain short-circuits instead of finishing an unnecessary traversal.

python
def is_balanced(root):
  def height(node):
    if not node:
      return 0
    left = height(node.left)
    if left == -1:
      return -1
    right = height(node.right)
    if right == -1:
      return -1
    if abs(left - right) > 1:
      return -1
    return max(left, right) + 1

  return height(root) != -1

Use a queue seeded with the root, and process one full level at a time by recording the queue's current size before draining it, so you know exactly where one level ends and the next begins.

python
from collections import deque

def level_order(root):
  if not root:
    return []
  result = []
  queue = deque([root])
  while queue:
    level_size = len(queue)
    level = []
    for _ in range(level_size):
      node = queue.popleft()
      level.append(node.val)
      if node.left:
        queue.append(node.left)
      if node.right:
        queue.append(node.right)
    result.append(level)
  return result

Pick a real example where the tradeoff actually mattered, not one where the "right" answer was obvious in hindsight. A useful structure: what the deadline pressure actually was, what you cut to hit it, and what broke or didn't break as a consequence. If nothing broke, say what made you confident enough to take that risk rather than just getting lucky.

The "would you decide differently now" part is where a lot of candidates go generic. A specific answer names one concrete thing you'd do differently, adding a feature flag, writing one more test around the risky path, getting one more person's review, rather than a vague "I'd be more careful."

Generic answers ("I love food delivery," "great culture") read as filler. A stronger answer connects something specific about Swiggy's actual technical or business challenges, the scale of real-time logistics matching, the catalog and pricing complexity across hundreds of thousands of restaurants, or a specific product like Instamart, to something concrete in your own background or interests.

It's fine to be candid that the compensation and role level matter too. What reads poorly is pretending those don't factor in at all, since every experienced interviewer knows they do, and pretending otherwise reads as coached rather than honest.

Pick a specific technology or domain you genuinely didn't know beforehand, not something adjacent to your existing skills dressed up as new. Describe how you actually learned it under time pressure, official docs, a specific engineer you asked, a small throwaway prototype, rather than a vague "I read up on it."

The stronger endings mention what you'd still want to learn more deeply if you had more time, since that shows the learning was real rather than just enough to get the ticket closed and forgotten.

A concrete answer names the actual criteria you used, customer impact, whether a workaround exists, blast radius, rather than a generic "I prioritize based on urgency and impact" statement that could apply to any situation. If you escalated or renegotiated the feature deadline instead of just quietly working overtime, say that plainly, since it shows judgment about when a deadline itself is the thing that should move.

Give a range grounded in research, not a single number pulled from your current pay stub. If you know your current total compensation, state it plainly along with what would make a move worthwhile, typically a meaningful jump given the effort and risk of switching, rather than a token increase.

Avoid answering with "what's the budget for this role" as your only response. It's a fair clarifying question to ask once, but HR usually still wants your number first, and dodging it twice reads as evasive rather than savvy.

State your actual contractual notice period plainly. If there's genuine flexibility, buyout options, an understanding manager, gardening leave that could be shortened, say so, but don't promise a timeline you haven't actually confirmed you can hit, since that becomes a problem for both sides if the current employer doesn't cooperate.

Being honest here works in your favor more often than it doesn't. If you're further along elsewhere, saying so, without bluffing about details you don't actually have, can genuinely speed up a decision on Swiggy's end. If you have nothing else in motion, it's fine to say that plainly rather than implying competition that doesn't exist.

Answer plainly and early rather than leaving it ambiguous through the technical rounds, since location constraints can be a hard blocker for a specific team regardless of how strong the technical performance was. If relocation depends on a timeline (lease ending, family logistics), say the actual timeline rather than a vague "I'm flexible."

Medium questions

23

Model each cell as a node whose single outgoing edge points to whichever cell its L, R, U, or D value sends you toward, then walk the path starting from your source cell, following exactly one edge at a time until you either land on the destination or start repeating a cell you've already visited.

Because each cell only has one outgoing edge, this collapses to a simple linked-list-style traversal rather than a full graph search: keep a visited set, follow the chain, and return false the moment you revisit a cell without having hit the target, since that means you're stuck in a loop that never reaches the destination.

Keep two heaps: a max-heap holding the smaller half of the numbers seen so far, and a min-heap holding the larger half, keeping them balanced in size (or off by at most one). The median is either the top of the larger heap, or the average of both tops if the heaps are equal in size.

python
import heapq

class MedianFinder:
  def __init__(self):
    self.small = [] # max-heap, store negatives
    self.large = [] # min-heap

  def add(self, num):
    heapq.heappush(self.small, -num)
    heapq.heappush(self.large, -heapq.heappop(self.small))
    if len(self.large) > len(self.small):
      heapq.heappush(self.small, -heapq.heappop(self.large))

  def median(self):
    if len(self.small) > len(self.large):
      return -self.small[0]
    return (-self.small[0] + self.large[0]) / 2

Each insertion is O(log n) for the heap push and pop, so the whole stream runs in O(n log n) versus O(n^2) for the naive approach of re-sorting after every number.

The trick that avoids extra space is to walk both lists to their ends, switching to the head of the other list the moment you hit null. Because both pointers together travel the same total distance (length of list A plus length of list B), they land on the intersection node at the same step, or both hit null at the same time if there's no intersection.

python
def get_intersection(a, b):
  p1, p2 = a, b
  while p1 is not p2:
    p1 = p1.next if p1 else b
    p2 = p2.next if p2 else a
  return p1

No length calculation up front, no hash set of visited nodes. Just two pointers and a redirect, O(n) time and O(1) extra space.

Keep an "in" stack for pushes and an "out" stack for pops. Enqueue always pushes onto "in." Dequeue pops from "out" if it has anything, otherwise it dumps the entire "in" stack onto "out" (reversing the order) and then pops from "out."

python
class QueueFromStacks:
  def __init__(self):
    self.in_stack = []
    self.out_stack = []

  def enqueue(self, x):
    self.in_stack.append(x)

  def dequeue(self):
    if not self.out_stack:
      while self.in_stack:
        self.out_stack.append(self.in_stack.pop())
    return self.out_stack.pop()

A single element only ever gets moved from "in" to "out" once over its lifetime, so even though a given dequeue call can occasionally be O(n) when it triggers the transfer, the amortized cost across any sequence of n operations is O(1) per call.

Instead of checking every substring for being a palindrome (O(n^3)), treat every index, and every gap between two indices, as a potential center, then expand outward from that center as long as the characters on both sides match. Track the longest match found across all centers.

python
def longest_palindrome(s):
  def expand(l, r):
    while l >= 0 and r < len(s) and s[l] == s[r]:
      l -= 1
      r += 1
    return s[l + 1:r]

  best = ""
  for i in range(len(s)):
    odd = expand(i, i)
    even = expand(i, i + 1)
    best = max(best, odd, even, key=len)
  return best

This runs in O(n^2) time with O(1) extra space, which is what most interviewers want to see before you mention Manacher's algorithm as the O(n) upgrade, which is rarely required unless the interviewer explicitly pushes for it.

Tail recursion means the recursive call is the very last operation in the function, with no pending work left to do after it returns, which in principle lets a compiler reuse the current stack frame instead of pushing a new one.

python
def fib_tail(n, a=0, b=1):
  if n == 0:
    return a
  return fib_tail(n - 1, b, a + b)

Python, and most JavaScript engines, don't guarantee tail-call elimination, so this still consumes O(n) stack frames in practice despite being written in tail form. The honest answer here is that tail recursion is a style choice that pays off in languages like Scheme or Elixir that guarantee the optimization, and an interview follow-up worth raising yourself is converting the same logic to a simple iterative loop, which sidesteps the whole question.

Because the array is sorted, every occurrence of a value sits contiguously. Binary search for the first and last index of each distinct value using two variants of binary search, first-occurrence and last-occurrence, and the count is last minus first plus one.

python
def first_last(arr, target):
  lo, hi = 0, len(arr) - 1
  first = last = -1
  while lo <= hi:
    mid = (lo + hi) // 2
    if arr[mid] == target:
      first = mid
      hi = mid - 1
    elif arr[mid] < target:
      lo = mid + 1
    else:
      hi = mid - 1
  lo, hi = 0, len(arr) - 1
  while lo <= hi:
    mid = (lo + hi) // 2
    if arr[mid] == target:
      last = mid
      lo = mid + 1
    elif arr[mid] < target:
      lo = mid + 1
    else:
      hi = mid - 1
  return last - first + 1

Each distinct value's count costs O(log n) instead of O(n), which is the answer interviewers are fishing for when they specifically say "better than O(n)" rather than just "count frequencies."

Each element has exactly two states with respect to any given subset: included or excluded. With n independent binary choices, the total number of distinct combinations is 2^n, one of which is the empty set and one of which is the full array.

python
def subsets(nums):
  result = [[]]
  for num in nums:
    result += [curr + [num] for curr in result]
  return result

Building it this way avoids explicit recursion: each pass over the existing result list doubles it by appending the current number to every subset already generated, which mirrors the 2^n growth directly instead of just asserting it.

Floyd's tortoise and hare handles detection: a slow pointer moves one node at a time, a fast pointer moves two, and if they ever meet, a cycle exists. Finding the start requires a second phase, once they meet, reset one pointer to the head and advance both one step at a time. The mathematics of the distances traveled guarantees they meet exactly at the cycle's start node.

python
def detect_cycle_start(head):
  slow = fast = head
  while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
    if slow is fast:
      break
  else:
    return None

  slow = head
  while slow is not fast:
    slow = slow.next
    fast = fast.next
  return slow

This runs in O(n) time and O(1) space, which is the entire reason interviewers prefer it over the simpler hash-set-of-visited-nodes approach that solves the same problem with O(n) extra space.

Track a running sum as you scan left to right. At each element, decide whether extending the previous subarray is still better than starting fresh at the current element, which is just comparing running_sum + current versus current alone.

python
def max_subarray(nums):
  best = current = nums[0]
  for num in nums[1:]:
    current = max(num, current + num)
    best = max(best, current)
  return best

The reset only happens implicitly through that max comparison, never by explicitly zeroing when you hit a negative number, which is the mistake a lot of candidates make when they try to write this from memory instead of re-deriving it. Explicitly zeroing on a negative running sum breaks the case where every number in the array is negative, since the answer there is the least negative single element, not zero.

Merging both arrays and picking the middle element is the obvious O(m+n) approach, and it's a fine answer to start with, but the faster version does binary search on the smaller array to find a partition point where every element on the left side of the combined partition is less than or equal to every element on the right side.

python
def find_median_sorted_arrays(a, b):
  if len(a) > len(b):
    a, b = b, a
  m, n = len(a), len(b)
  lo, hi = 0, m
  while lo <= hi:
    i = (lo + hi) // 2
    j = (m + n + 1) // 2 - i
    a_left = a[i - 1] if i > 0 else float("-inf")
    a_right = a[i] if i < m else float("inf")
    b_left = b[j - 1] if j > 0 else float("-inf")
    b_right = b[j] if j < n else float("inf")

    if a_left <= b_right and b_left <= a_right:
      if (m + n) % 2 == 0:
        return (max(a_left, b_left) + min(a_right, b_right)) / 2
      return max(a_left, b_left)
    elif a_left > b_right:
      hi = i - 1
    else:
      lo = i + 1

This runs in O(log(min(m, n))) since the binary search only happens on the smaller array. Most candidates get partial credit stopping at the merge-based O(m+n) version, but interviewers who ask this specifically are usually checking whether you know the binary-search-on-partition trick exists at all.

Fee calculation shouldn't live inside ParkingSlot or Ticket, since pricing rules (hourly rate, day-rate cap, vehicle-type multiplier) change independently of the physical slot model. Put it behind a separate FeeStrategy interface that takes a Ticket's entry and exit time plus vehicle type and returns an amount.

python
class HourlyFeeStrategy:
  def __init__(self, rate_per_hour):
    self.rate = rate_per_hour

  def calculate(self, ticket, exit_time):
    hours = max(1, (exit_time - ticket.entry_time).seconds // 3600)
    return hours * self.rate

Swapping the strategy object at construction time (a flat day rate for one lot, tiered hourly for another) means the rest of the system, entry, slot assignment, ticket issuance, never has to change. That's the actual thing a machine coding grader is checking for under "design a parking lot": not whether you can park a car, but whether your class boundaries survive a requirements change.

Store every expense amount in its original currency plus a currency code, and never convert at write time. Conversion should happen only at read time, when computing a user's net balance for display or settlement, using an exchange rate service that's swappable behind an interface, since baking a specific rate into stored data means old balances silently drift wrong every time rates update.

python
class Money:
  def __init__(self, amount, currency):
    self.amount = amount
    self.currency = currency

def convert(money, target_currency, rate_service):
  if money.currency == target_currency:
    return money
  rate = rate_service.get_rate(money.currency, target_currency)
  return Money(money.amount * rate, target_currency)

The follow-up worth anticipating is what happens to a group where different members' home currencies differ. The clean answer is that the ledger stores balances per currency pair, and settlement suggestions get converted to whichever currency the two settling users agree on at the moment they settle, not at expense-creation time.

Keep a small command history, each parking action recorded as a Command object with an execute and an undo method, rather than trying to reverse-engineer the previous state from the current one. Undo just calls undo() on the most recent command and pops it off the history stack.

python
class ParkCommand:
  def __init__(self, slot, vehicle):
    self.slot = slot
    self.vehicle = vehicle

  def execute(self):
    self.slot.vehicle = self.vehicle

  def undo(self):
    self.slot.vehicle = None

class CommandHistory:
  def __init__(self):
    self.history = []

  def do(self, command):
    command.execute()
    self.history.append(command)

  def undo_last(self):
    if self.history:
      self.history.pop().undo()

This is a fairly standard command-pattern add-on, and interviewers use it to check whether you reach for a known pattern cleanly or start bolting ad hoc state-tracking flags onto ParkingSlot itself, which gets messy fast once more than one type of reversible action exists.

Sharding by restaurant_id is the intuitive first instinct and the wrong one at exactly the moment it matters most, a single very popular restaurant during dinner rush becomes a hot shard that overwhelms one node while every other shard sits idle. Sharding by a combination of geography and time bucket, or by customer_id instead, spreads that same restaurant's orders across many shards since its customers aren't concentrated on one node.

The tradeoff to name out loud is that customer_id sharding makes "all orders for restaurant X" a scatter-gather query across every shard instead of a single-shard lookup. That's an acceptable cost since restaurant-level order history is a much less latency-sensitive read than the order-placement write path itself, which is the operation that actually needs to survive a dinner-rush spike.

Ordinary least squares for one feature reduces to two closed-form formulas: the slope is the covariance of x and y divided by the variance of x, and the intercept is the mean of y minus the slope times the mean of x.

python
class SimpleLinearRegression:
  def fit(self, x, y):
    n = len(x)
    x_mean = sum(x) / n
    y_mean = sum(y) / n
    numerator = sum((x[i] - x_mean) * (y[i] - y_mean) for i in range(n))
    denominator = sum((x[i] - x_mean) ** 2 for i in range(n))
    self.slope = numerator / denominator
    self.intercept = y_mean - self.slope * x_mean

  def predict(self, x):
    return [self.slope * xi + self.intercept for xi in x]

Interviewers typically follow up by asking what breaks with this closed-form approach once you add more features. The honest answer is that it doesn't generalize cleanly, multiple linear regression needs the normal equation with matrix inversion, or gradient descent, since covariance and variance of a single feature no longer capture the relationship once features are correlated with each other.

This is a straightforward conditional aggregation once you frame "under 30 minutes" as a CASE expression inside a SUM, rather than reaching for a subquery per restaurant.

sql
SELECT
 restaurant_id,
 COUNT(*) AS total_orders,
 SUM(CASE WHEN delivery_minutes < 30 THEN 1 ELSE 0 END) AS fast_orders,
 ROUND(
  SUM(CASE WHEN delivery_minutes < 30 THEN 1 ELSE 0 END) * 1.0 / COUNT(*),
  3
 ) AS fast_ratio
FROM orders
GROUP BY restaurant_id
ORDER BY fast_ratio DESC;

Watch the integer division trap: without the `* 1.0`, most SQL engines truncate the ratio to zero whenever fast_orders is smaller than total_orders, which it almost always is.

Start from a population anchor rather than guessing a raw number: a city of roughly 10 million people, assume 30 percent order food delivery at least occasionally, and of those, maybe 20 percent order on a given day, averaging 1.2 orders each. That's roughly 720,000 orders a day, or about 500 orders a minute if demand were flat, though real demand clusters hard around lunch and dinner, so peak-minute volume is several times that average.

On increasing the number, the honest framing is that "more orders" splits into two separate levers that need separate answers: growing the number of people who order at all (acquisition, new-user promos, expanding restaurant selection into underserved neighborhoods) versus increasing order frequency among people who already order (reducing delivery time, better restaurant discovery, subscription-style loyalty perks). A good answer picks one lever, states an assumption about which is more tractable for a marketplace at Swiggy's maturity, and reasons through a specific experiment rather than listing both levers generically.

Frame it as a unit economics question before a market-sizing one. Ten-minute delivery lives or dies on dark store density, since delivery time is mostly a function of distance from a fulfillment point to the customer, not rider speed. Before talking demand, work out how many micro-fulfillment centers a city needs to guarantee sub-10-minute coverage for a target population density, then price out the fixed cost of that footprint against expected order volume and basket size.

The follow-up worth anticipating is what happens to unit economics on a low-density order, a single ₹150 basket delivered 3 km away. The real answer is that quick commerce margins depend heavily on order frequency per dark store per hour, so the actual decision criterion is whether a new city can sustain enough order density per store to make the fixed cost of that density pay back, not just whether people want the service.

DENSE_RANK is the cleanest tool here, since it handles ties correctly (two orders with the identical top value both get rank 1, and the next distinct value gets rank 2) in a way a naive self-join comparing every pair of orders doesn't.

sql
WITH ranked AS (
 SELECT
  customer_id,
  order_value,
  DENSE_RANK() OVER (
   PARTITION BY customer_id ORDER BY order_value DESC
  ) AS rnk
 FROM orders
)
SELECT customer_id, order_value
FROM ranked
WHERE rnk = 2;

The follow-up interviewers like to ask is what happens to a customer with only one order. That customer correctly disappears from the result set entirely, since there's no row with rnk = 2 for them, which is the expected behavior for "second highest," not a bug to patch around.

Overall week-over-week order counts are confounded by everything else happening that week, a holiday, a marketing push, weather. The clean answer is a randomized A/B test, splitting users (or better, splitting restaurant-impression sessions) between the old ranking and the new one, and measuring conversion rate, orders per session that saw a restaurant listing, not raw order count.

The detail worth raising unprompted is guarding against network effects between the two arms. If the new ranking surfaces a restaurant that then runs out of a menu item because both arms are pulling from the same limited inventory, that contaminates the comparison, so the experiment needs either restaurant-level randomization or a large enough restaurant pool that inventory contention between arms stays negligible.

The interviewer is checking two things at once: whether you can hold a technical position under pushback, and whether you know when to stop pushing once a decision is made. A strong answer describes the actual disagreement concretely (not "I thought we should use microservices" in the abstract, but the specific tradeoff at stake), how you made your case, in writing or in a meeting, with data or a small prototype rather than just opinion, and what happened once the decision went the other way.

Answers that end with "and I was right, they later admitted it" read as score-settling rather than self-awareness. The stronger ending acknowledges what you learned even if the outcome validated your original position, since the point of the question is judgment under disagreement, not who was correct.

The trap here is describing something you were assigned to do that you just did well. Ownership questions are looking for the gap between "my ticket" and "the thing that actually needed doing." A concrete example: noticing a recurring on-call page pointed at a flaky dependency nobody owned, and spending a week fixing the root cause instead of just re-triaging the alert each time it fired.

Name the actual cost of not acting, what would have kept happening if you hadn't stepped in, since that's what makes the ownership claim concrete rather than a values-statement.

Hard questions

3

Do the integer division first, then track the long-division remainder at each step in a hash map pointing remainder to the position in the output string where that remainder first appeared. The moment a remainder repeats, everything between its first appearance and now is the repeating block, so you insert parentheses there and stop.

python
def fraction_to_string(numerator, denominator):
  if numerator % denominator == 0:
    return str(numerator // denominator)

  sign = "-" if (numerator < 0) != (denominator < 0) else ""
  num, den = abs(numerator), abs(denominator)
  result = [sign + str(num // den), "."]
  remainder = num % den
  seen = {}

  while remainder != 0:
    if remainder in seen:
      result.insert(seen[remainder], "(")
      result.append(")")
      break
    seen[remainder] = len(result)
    remainder *= 10
    result.append(str(remainder // den))
    remainder %= den

  return "".join(result)

The map-of-remainders trick is the whole problem. Without it you'd loop forever on any fraction that doesn't terminate cleanly.

Back the map with a fixed-size array of buckets, where each bucket is a small list of key-value pairs. Hash the key to pick a bucket, then linearly scan that bucket's list for a matching key on get, put, or remove. Rehash into a larger array once the load factor crosses a threshold, typically 0.75, to keep bucket lists short.

python
class MyHashMap:
  def __init__(self, capacity=16):
    self.capacity = capacity
    self.buckets = [[] for _ in range(capacity)]

  def _index(self, key):
    return hash(key) % self.capacity

  def put(self, key, value):
    bucket = self.buckets[self._index(key)]
    for i, (k, v) in enumerate(bucket):
      if k == key:
        bucket[i] = (key, value)
        return
    bucket.append((key, value))

  def get(self, key):
    bucket = self.buckets[self._index(key)]
    for k, v in bucket:
      if k == key:
        return v
    raise KeyError(key)

The follow-up interviewers usually push toward is what happens under a bad hash function that sends everything to one bucket. The honest answer is the whole structure degrades to a linked list, O(n) per operation, which is why real implementations either use a good hash or fall back to a balanced tree per bucket once it grows past a size threshold, the way Java's HashMap does since version 8.

Reduce every user to a single net balance (total owed to them minus total they owe), then greedily match the person with the largest positive balance against the person with the largest negative balance, settling the smaller of the two amounts and repeating until every balance is zero.

python
import heapq

def simplify_debts(net_balances):
  creditors = [(-amt, user) for user, amt in net_balances.items() if amt > 0]
  debtors = [(amt, user) for user, amt in net_balances.items() if amt < 0]
  heapq.heapify(creditors)
  heapq.heapify(debtors)
  transactions = []

  while creditors and debtors:
    credit, cred_user = heapq.heappop(creditors)
    debit, deb_user = heapq.heappop(debtors)
    settle = min(-credit, -debit)
    transactions.append((deb_user, cred_user, settle))
    credit += settle
    debit += settle
    if credit < 0:
      heapq.heappush(creditors, (credit, cred_user))
    if debit < 0:
      heapq.heappush(debtors, (debit, deb_user))

  return transactions

This greedy approach doesn't always hit the mathematically minimal transaction count in every edge case, but it's the answer most machine coding rubrics accept, and it's a defensible tradeoff to state out loud: true minimum-transaction settlement is an NP-hard subset-sum variant, so a greedy heuristic is the practical engineering choice here, not a shortcut you took because you didn't know better.

Real-time scenario questions

6

Start with a User class, a Group class holding a list of users, and an Expense class that stores who paid, the total amount, and a list of Split objects, one per participant. The trick that keeps this extensible is making Split an interface (or abstract base) with an EqualSplit, ExactSplit, and PercentSplit implementation, each responsible for computing that participant's share.

python
class Split:
  def __init__(self, user, amount=0):
    self.user = user
    self.amount = amount

class Expense:
  def __init__(self, paid_by, total, splits):
    self.paid_by = paid_by
    self.total = total
    self.splits = splits # list of Split, already resolved to amounts

  def validate(self):
    if abs(sum(s.amount for s in self.splits) - self.total) > 0.01:
      raise ValueError("splits don't sum to total")

class Ledger:
  def __init__(self):
    self.balances = {} # (user_a, user_b) -> net amount a owes b

  def add_expense(self, expense):
    for split in expense.splits:
      if split.user != expense.paid_by:
        key = (split.user, expense.paid_by)
        self.balances[key] = self.balances.get(key, 0) + split.amount

The two things interviewers actually watch for: whether you validate that exact splits sum to the total before accepting the expense, and whether your balance simplification handles the case where A owes B and B separately owes A, netting those into one direction instead of tracking both.

Model ParkingLot as a collection of ParkingFloor objects, each holding a list of ParkingSlot objects sized for different vehicle types (motorcycle, car, truck). On entry, search floors in order, and within a floor pick the lowest-numbered free slot matching the vehicle's size class, which naturally approximates "nearest to entry" if slot numbering follows physical distance from the gate.

python
class ParkingSlot:
  def __init__(self, slot_id, size):
    self.slot_id = slot_id
    self.size = size
    self.vehicle = None

  def is_free(self):
    return self.vehicle is None

class ParkingLot:
  def __init__(self, floors):
    self.floors = floors

  def park(self, vehicle):
    for floor in self.floors:
      for slot in floor.slots:
        if slot.is_free() and slot.size == vehicle.size:
          slot.vehicle = vehicle
          return Ticket(vehicle, slot)
    raise Exception("lot full")

The detail that separates a passing submission from a strong one is thinking about concurrency up front, two attendants issuing tickets at the same time shouldn't be able to assign the same slot twice, which means the slot search-and-assign needs to happen under a lock or as one atomic compare-and-swap, not as two separate read-then-write steps.

Model the catalog as a tree: Restaurant has Categories, Category has Items, and each Item has zero or more VariantGroups (mutually exclusive choices like Small/Medium/Large) and zero or more AddOnGroups (optional extras like Extra Cheese, Extra Chutney, each individually selectable). Base price lives on the Item; each Variant and AddOn carries its own price delta.

python
class VariantGroup:
  def __init__(self, name, variants, required=True):
    self.name = name     # e.g. "Size"
    self.variants = variants # [(name, price_delta),...]
    self.required = required

class AddOnGroup:
  def __init__(self, name, addons, max_selectable):
    self.name = name
    self.addons = addons   # [(name, price_delta),...]
    self.max_selectable = max_selectable

def compute_price(item, selected_variant, selected_addons):
  price = item.base_price + selected_variant.price_delta
  price += sum(a.price_delta for a in selected_addons)
  return price

The design constraint interviewers push on is enforcing group rules at write time, a required variant group must have exactly one selection, and an add-on group with max_selectable=2 can't accept three, rather than trusting the client to send valid data. That validation belongs server-side in the order-creation path, not just in UI form logic.

Core entities are User, CalendarEvent (with start time, end time, owner, and organizer), and Invite (per-invitee record with status: pending, accepted, rejected). Free/busy lookups are the operation that needs to be fast, so store each user's confirmed events in a structure that supports fast overlap queries, an interval tree or a sorted list of intervals per user, rather than scanning every event on read.

python
class Invite:
  def __init__(self, event, invitee):
    self.event = event
    self.invitee = invitee
    self.status = "pending"

def has_conflict(user_intervals, new_start, new_end):
  # user_intervals sorted by start time
  import bisect
  idx = bisect.bisect_left(user_intervals, (new_start,))
  if idx > 0 and user_intervals[idx - 1][1] > new_start:
    return True
  if idx < len(user_intervals) and user_intervals[idx][0] < new_end:
    return True
  return False

A detail worth raising unprompted is that a rejected or tentative invite shouldn't block the invitee's free/busy slot, only accepted events should count toward conflict detection, which is a subtle source of bugs if the schema doesn't cleanly separate "invited" from "confirmed."

Separate authentication (who is this user) from authorization (what can this user do to this specific resource) cleanly. Use a Resource-Permission mapping, effectively an access control list per file or folder, rather than baking role checks into the upload handler itself.

python
class Permission:
  READ, WRITE, DELETE = "read", "write", "delete"

class AccessControlEntry:
  def __init__(self, user_id, resource_id, permissions):
    self.user_id = user_id
    self.resource_id = resource_id
    self.permissions = set(permissions)

def can_perform(acl_store, user_id, resource_id, action):
  entry = acl_store.get((user_id, resource_id))
  return entry is not None and action in entry.permissions

For a real system, this ACL check needs to run before the file bytes ever hit storage, not after, and inherited permissions (a folder's ACL applying to files inside it unless overridden) is the follow-up question interviewers usually reach for once the base design is on the whiteboard.

This one isn't drawn directly from a specific candidate transcript, but it's the natural extension of the catalog and ordering questions candidates do report, and it's worth having an answer ready for a food-delivery company specifically. Frame it as a real-time matching problem: partners publish location updates on a short interval, orders come in with a restaurant location and a delivery address, and a matching service scores nearby available partners by distance, current load, and estimated pickup time, then assigns the best match with a short acceptance window before falling back to the next candidate.

The parts worth calling out explicitly: geospatial indexing (a geohash or H3-style grid rather than scanning every partner's raw coordinates), an acceptance timeout so one partner declining doesn't stall the whole order, and idempotency on the assignment write, since partner location updates and order assignment both need to survive retries without double-booking a partner to two orders at once.

How to prepare for a Swiggy interview

Given how consistently the machine coding round shows up in candidate reports, treat it as a first-class prep item, not an afterthought squeezed in after DSA grinding. Practice building a small system end to end in under two hours: define your classes, get something running, then explain out loud why you split responsibilities the way you did. Reading a Splitwise design writeup is not the same skill as typing one out under a clock with someone watching you make decisions in real time.

For the DSA round, the pattern across reported questions is not really about exotic algorithms. It is about linked lists, trees, recursion, and two-pointer techniques executed cleanly and explained clearly, rather than one flashy hard problem solved silently. For system design and domain rounds, the food-delivery specifics, catalog modeling with variants and add-ons, real-time partner matching, quick commerce unit economics, are worth rehearsing directly rather than relying on generic system design templates built for a different kind of company.

Get the reps in before the real thing

Reading through a machine coding writeup is not the same as producing working code for a Splitwise clone while someone watches the clock. LastRoundAI's mock interview mode runs live coding and system design rounds with real-time follow-up questions in your browser, and the free plan includes 15 credits a month that reset monthly rather than piling up unused. Starter is $19/mo if a handful of sessions isn't enough runway before a loop like this one.

Once your answers hold up under a follow-up, the slower part is usually just getting in front of enough roles that actually run a loop like Swiggy's rather than a generic screen. Auto-Apply queues tailored applications for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.

Questions about either product go to contact@lastroundai.com. That's the only inbox we check.

How this list was built

Worth being straight about where these questions come from, because plenty of pages in this category are not. The set was compiled from a research pass across official documentation, vendor release notes, published engineering writing and public discussion of hiring processes, then cross-checked against the current version of each technology so nothing here describes behaviour that has since changed.

What that means in practice: these are the questions the material supports as reasonable and current for this role, not a transcript of any one company's loop. We have not sat in on your interview and we are not going to claim we have. Treat the list as well-sourced preparation rather than a leaked question bank, and expect your panel to phrase things their own way.

If you spot something out of date, tell us at contact@lastroundai.com and we will fix it.

Frequently asked questions

How hard is the Swiggy interview?

Hard, but the difficulty is more about depth of follow-up than exotic questions. Interviewers tend to take a reasonable problem and keep pushing on trade-offs, edge cases and what you would change at ten times the load. Preparing to be interrogated on an answer matters more than memorising more answers.

What coding level should I prepare for a Swiggy interview?

Comfortable medium-difficulty problems solved cleanly while talking, rather than hard problems solved silently. Most rejections come from unclear communication and untested edge cases, not from failing to find an exotic algorithm.

Does Swiggy ask system design at every level?

From mid-level upward, almost always, and it carries increasing weight as you go up. Junior loops may include a lighter version focused on structuring a feature rather than designing a distributed system.

What behavioural signals does Swiggy look for?

Concrete ownership stories with a real outcome. Vague team-level answers score poorly; interviewers are listening for what you specifically did, what it cost, and what you learned when it went wrong.

How long is the Swiggy hiring process?

Often three to eight weeks end to end, with the gap between onsite and decision being the slowest part. Team matching, where it applies, can add further time and is not a reflection on your performance.

Leave a Reply

Your email address will not be published. Required fields are marked *