Company Guide · Juspay

Juspay Interview Preparation 2026: Hiring Challenge, Functional Programming and Salary

How the Juspay hiring challenge is commonly structured, fully solved versions of its signature graph and tree-locking problems, the functional programming you should know, and how internships convert to full-time offers.

✍️ Pranjal Jain, Ex-Microsoft · IIT Kanpur 📅 10 October 2026 ⏱ 17 min read
Illustration of a directed graph with cycles and functional programming light trails
TL;DR
  • Juspay (Bengaluru) builds payments infrastructure: checkout SDKs, payment orchestration, UPI stacks and the open-source Hyperswitch. Much of its codebase uses Haskell, PureScript and Rust.
  • Freshers typically enter via the Juspay hiring challenge: an online coding round, a development or problem-solving stage, and interviews. Rounds vary by edition.
  • The coding round is famous for graphs where every node has at most one outgoing edge: maximum weight node, nearest meeting cell, largest sum cycle. Solved below.
  • Interviews frequently feature the m-ary tree lock/unlock/upgrade problem, often with a thread-safety follow-up.
  • Freshers usually join as interns (often around six months) with conversion to full-time based on performance. Approximate public-source FTE offers: roughly 18 to 30 LPA. Confirm current terms with Juspay.

Juspay is one of the few Indian companies where the interview tells you something real about the job. The coding problems are tight graph and tree puzzles that reward precise reasoning, the follow-ups probe concurrency because payment systems are concurrent, and the company openly bets on functional programming, which most Indian graduates have never used.

This guide covers the commonly reported hiring challenge format, gives complete solutions to the problems that appear most often in candidate write-ups (in Python and Java with dry runs), explains the tree locking problem and its concurrency follow-up, introduces the Haskell and PureScript ideas worth knowing, and explains how internships convert to full-time roles. Juspay changes the challenge structure between editions, so always read the official announcement for your cycle.

About Juspay: What the Company Builds

Juspay builds payment infrastructure used by large Indian merchants and banks. Its products include checkout and payment SDKs used inside many popular apps, payment orchestration that routes transactions across gateways, UPI technology stacks for banks, and Hyperswitch, an open-source payments switch written in Rust. When you pay inside a well-known Indian app, there is a reasonable chance some Juspay software is in the path.

Technically, Juspay is known for unusual choices. Large parts of its backend have been written in Haskell, its client-side and SDK work has used PureScript (a Haskell-like language that compiles to JavaScript), and newer infrastructure such as Hyperswitch uses Rust. The common thread is strong static typing: in payments, catching a class of bugs at compile time is worth a lot.

The Juspay Hiring Challenge: Commonly Reported Structure

Round 1

Online coding

A few algorithmic problems on an online platform, frequently graph-based, with hidden test cases. Usually 60 to 120 minutes.

Round 2

Development / problem round

Some editions include a longer build or problem-solving stage that evaluates code quality and design, not only correctness.

Round 3

Technical interviews

One or two interviews: DSA (often the tree locking problem), your code from earlier rounds, CS fundamentals, projects.

Round 4

Culture / leadership

Motivation, learning attitude, openness to functional programming, and how you handle ambiguity.

⚠️
Read the edition rulesJuspay has run the challenge in different forms over the years: campus drives, off-campus registrations, and variants with or without a development round. Eligibility (graduation year, branch) and the conversion terms also change. Treat this guide as the typical shape, not a guarantee.

Solved: Juspay's Signature Graph Problems

The classic problem set uses a functional graph: N cells numbered 0 to N−1, and an array edge where edge[i] is the single cell that cell i exits to, or −1 if it has no exit. Because every node has out-degree at most one, each connected component is either a chain ending at −1, or a single cycle with trees hanging into it. That structure makes O(N) solutions possible, which is the point of the problems.

Problem 1: Maximum weight node

The weight of a cell is the sum of the indices of all cells that point to it. Return the cell with the maximum weight; if several tie, return the largest index.

Python
def max_weight_node(edge):
    n = len(edge)
    weight = [0] * n
    for i, e in enumerate(edge):
        if e != -1:
            weight[e] += i
    best = 0
    for i in range(n):
        if weight[i] >= weight[best]:   # >= so ties pick the larger index
            best = i
    return best

print(max_weight_node([2, 0, -1, 2]))  # cell 2 gets 0 + 3 = 3 -> 2

O(N) time and space. The only trap is the tie rule, so read the statement carefully: some versions say largest index, others smallest.

Problem 2: Nearest meeting cell

Given two cells C1 and C2, find a cell reachable from both that minimises the total distance dist(C1, x) + dist(C2, x). Return −1 if none exists. (Some versions minimise the maximum of the two distances instead, as in LeetCode 2359; the method is identical, only the final comparison changes.)

Because each node has one outgoing edge, the path from a start node is a simple walk: follow edges until you hit −1 or revisit a node. Record distances from C1, then from C2, then compare.

Python
def distances(edge, start):
    dist = [-1] * len(edge)
    d, cur = 0, start
    while cur != -1 and dist[cur] == -1:
        dist[cur] = d
        d += 1
        cur = edge[cur]
    return dist

def nearest_meeting_cell(edge, c1, c2):
    d1, d2 = distances(edge, c1), distances(edge, c2)
    best, best_cost = -1, float("inf")
    for i in range(len(edge)):
        if d1[i] != -1 and d2[i] != -1:
            cost = d1[i] + d2[i]
            if cost < best_cost:
                best, best_cost = i, cost
    return best

edge = [4, 4, 1, 4, 13, 8, 8, 8, 0, 8, 14, 9, 15, 11, -1, 10, 15, 22, 22, 22, 22, 22, 21]
print(nearest_meeting_cell(edge, 9, 2))  # 4
Java
import java.util.Arrays;

public class NearestMeetingCell {
    static int[] distances(int[] edge, int start) {
        int[] dist = new int[edge.length];
        Arrays.fill(dist, -1);
        int d = 0, cur = start;
        while (cur != -1 && dist[cur] == -1) {
            dist[cur] = d++;
            cur = edge[cur];
        }
        return dist;
    }

    static int nearest(int[] edge, int c1, int c2) {
        int[] d1 = distances(edge, c1), d2 = distances(edge, c2);
        int best = -1;
        long bestCost = Long.MAX_VALUE;
        for (int i = 0; i < edge.length; i++) {
            if (d1[i] != -1 && d2[i] != -1 && (long) d1[i] + d2[i] < bestCost) {
                bestCost = (long) d1[i] + d2[i];
                best = i;
            }
        }
        return best;
    }

    public static void main(String[] args) {
        int[] edge = {4, 4, 1, 4, 13, 8, 8, 8, 0, 8, 14, 9, 15, 11, -1, 10, 15, 22, 22, 22, 22, 22, 21};
        System.out.println(nearest(edge, 9, 2)); // 4
    }
}

Dry run for C1 = 9, C2 = 2 on the sample: from 9 the walk is 9 → 8 → 0 → 4 → 13 → 11 → 9 (revisit, stop), giving distances 9:0, 8:1, 0:2, 4:3, 13:4, 11:5. From 2 the walk is 2 → 1 → 4 → 13 → 11 → 9 → 8 → 0 → 4 (revisit), giving 2:0, 1:1, 4:2, 13:3, 11:4, 9:5, 8:6, 0:7. Common cells and total costs: 4 → 3+2 = 5, 13 → 4+3 = 7, 11 → 5+4 = 9, 9 → 0+5 = 5, 8 → 1+6 = 7, 0 → 2+7 = 9. Cells 4 and 9 tie at 5; scanning in index order keeps the first, cell 4. If the problem specifies a different tie rule, adjust the comparison. Each walk is O(N), so the whole solution is O(N).

Problem 3: Largest sum cycle

Return the maximum, over all cycles, of the sum of node indices in the cycle, or −1 if there is no cycle. Use the three-state colouring trick for functional graphs: walk from every unvisited node, marking nodes with the id of the current walk. If you reach a node marked with the current walk id, you found a new cycle; sum it. If you reach a node from an earlier walk or −1, stop. Every node is visited once, so it is O(N).

Python
def largest_sum_cycle(edge):
    n = len(edge)
    walk_id = [-1] * n          # which walk first visited each node
    best = -1
    for start in range(n):
        if walk_id[start] != -1:
            continue
        cur = start
        while cur != -1 and walk_id[cur] == -1:
            walk_id[cur] = start
            cur = edge[cur]
        if cur != -1 and walk_id[cur] == start:   # cycle found in this walk
            total, node = cur, edge[cur]
            while node != cur:
                total += node
                node = edge[node]
            best = max(best, total)
    return best

print(largest_sum_cycle([4, 4, 1, 4, 13, 8, 8, 8, 0, 8, 14, 9, 15, 11, -1, 10, 15, 22, 22, 22, 22, 22, 21]))  # 45
Java
static long largestSumCycle(int[] edge) {
    int n = edge.length;
    int[] walkId = new int[n];
    java.util.Arrays.fill(walkId, -1);
    long best = -1;
    for (int start = 0; start < n; start++) {
        if (walkId[start] != -1) continue;
        int cur = start;
        while (cur != -1 && walkId[cur] == -1) {
            walkId[cur] = start;
            cur = edge[cur];
        }
        if (cur != -1 && walkId[cur] == start) {
            long total = cur;
            for (int node = edge[cur]; node != cur; node = edge[node]) total += node;
            best = Math.max(best, total);
        }
    }
    return best;
}

On the sample, the cycle 4 → 13 → 11 → 9 → 8 → 0 → 4 sums to 4 + 13 + 11 + 9 + 8 + 0 = 45, and 21 → 22 → 21 sums to 43, so the answer is 45. Use long for the sum in Java: with N up to 10^5 or more, index sums can exceed the int range. That is exactly the kind of hidden test case that separates passing from failing. Our graph BFS and DFS guide covers cycle detection more generally.

The Juspay Tree Locking Problem (Lock, Unlock, Upgrade)

This problem is widely reported in Juspay interviews. You have an m-ary tree of N nodes. Implement three operations, each taking a node and a user id:

  • lock(X, uid): succeeds if X is not locked, no ancestor of X is locked, and no descendant of X is locked. Then X is locked by uid.
  • unlock(X, uid): succeeds only if X is locked by the same uid.
  • upgradeLock(X, uid): succeeds if X is not locked, no ancestor is locked, X has at least one locked descendant, and all locked descendants are locked by uid. Then all those descendants are unlocked and X is locked by uid.

The naive solution scans the whole subtree on every lock, which is O(N) per call. The efficient idea: store in each node a count of locked descendants. Then "no descendant is locked" is an O(1) check, and lock/unlock only walk the ancestor chain, O(h) where h is the tree height (O(log_m N) for a complete tree).

Java
import java.util.*;

public class TreeOfSpace {
    static class Node {
        final String name;
        Node parent;
        final List<Node> children = new ArrayList<>();
        int lockedBy = -1;
        int lockedDescendants = 0;
        Node(String name) { this.name = name; }
    }

    private boolean ancestorLocked(Node x) {
        for (Node p = x.parent; p != null; p = p.parent)
            if (p.lockedBy != -1) return true;
        return false;
    }

    private void updateAncestors(Node x, int delta) {
        for (Node p = x.parent; p != null; p = p.parent) p.lockedDescendants += delta;
    }

    public boolean lock(Node x, int uid) {
        if (x.lockedBy != -1 || x.lockedDescendants > 0 || ancestorLocked(x)) return false;
        x.lockedBy = uid;
        updateAncestors(x, +1);
        return true;
    }

    public boolean unlock(Node x, int uid) {
        if (x.lockedBy != uid) return false;
        x.lockedBy = -1;
        updateAncestors(x, -1);
        return true;
    }

    public boolean upgradeLock(Node x, int uid) {
        if (x.lockedBy != -1 || x.lockedDescendants == 0 || ancestorLocked(x)) return false;
        List<Node> locked = new ArrayList<>();
        collectLocked(x, locked);
        for (Node d : locked) if (d.lockedBy != uid) return false;
        for (Node d : locked) unlock(d, uid);
        return lock(x, uid);
    }

    // Visit only subtrees that contain locked nodes
    private void collectLocked(Node x, List<Node> out) {
        for (Node c : x.children) {
            if (c.lockedBy != -1) out.add(c);
            if (c.lockedDescendants > 0) collectLocked(c, out);
        }
    }

    public static void main(String[] args) {
        // Build a 2-ary tree in level order: World -> Asia, Africa -> India, China, Egypt, SouthAfrica
        String[] names = {"World", "Asia", "Africa", "India", "China", "Egypt", "SouthAfrica"};
        int m = 2;
        Node[] nodes = new Node[names.length];
        for (int i = 0; i < names.length; i++) nodes[i] = new Node(names[i]);
        for (int i = 0; i < names.length; i++)
            for (int k = 1; k <= m && m * i + k < names.length; k++) {
                nodes[i].children.add(nodes[m * i + k]);
                nodes[m * i + k].parent = nodes[i];
            }
        TreeOfSpace t = new TreeOfSpace();
        System.out.println(t.lock(nodes[3], 9));        // India by 9: true
        System.out.println(t.lock(nodes[4], 9));        // China by 9: true
        System.out.println(t.lock(nodes[1], 9));        // Asia: false (descendants locked)
        System.out.println(t.upgradeLock(nodes[1], 9)); // Asia upgrade by 9: true
        System.out.println(t.unlock(nodes[3], 9));      // India already released: false
        System.out.println(t.lock(nodes[0], 3));        // World: false (descendant Asia locked)
    }
}

Complexity and the follow-up interviewers love

lock and unlock are O(h). upgradeLock is O(h + k·h + size of explored subtrees) where k is the number of locked descendants, since collection prunes subtrees with no locks. Then comes the follow-up: make it thread-safe for concurrent calls without a single global lock. Good discussion points:

  • A global synchronized is correct but serialises everything; say so first as the baseline.
  • Per-node locks acquired in a consistent order (root to target) avoid deadlock, but locking the whole ancestor chain still contends heavily at the root.
  • An optimistic approach: atomically claim the target node with compare-and-set on lockedBy, then validate ancestors and increment counters atomically; on any conflict, roll back the changes made so far and return false.
  • Discuss the race you must prevent: two users locking a parent and a child at the same time, each seeing the other as unlocked.

You do not need a perfect lock-free design. Interviewers mostly want to see that you can identify the races, propose a correct scheme, and reason about its cost. The Java concurrency guide covers the primitives you would use.

Haskell and PureScript: What You Should Know Before Joining

You will not be asked to write production Haskell in the interview, but knowing the core ideas helps you answer "why do you want to work here" honestly and shows you can learn fast. Here are the concepts that matter, illustrated with a payments flavour.

Pure functions, immutability and Either for errors

Haskell
data PaymentError = InvalidAmount | UnsupportedCurrency String
  deriving Show

data Payment = Payment { amount :: Integer, currency :: String }
  deriving Show

validateAmount :: Integer -> Either PaymentError Integer
validateAmount a
  | a <= 0    = Left InvalidAmount
  | otherwise = Right a

validateCurrency :: String -> Either PaymentError String
validateCurrency c
  | c `elem` ["INR", "USD"] = Right c
  | otherwise               = Left (UnsupportedCurrency c)

-- Applicative style: the first error short-circuits
mkPayment :: Integer -> String -> Either PaymentError Payment
mkPayment a c = Payment <$> validateAmount a <*> validateCurrency c

-- The same thing with do-notation
mkPayment' :: Integer -> String -> Either PaymentError Payment
mkPayment' a c = do
  a' <- validateAmount a
  c' <- validateCurrency c
  pure (Payment a' c')

totalPaise :: [Payment] -> Integer
totalPaise = foldr (\p acc -> amount p + acc) 0

main :: IO ()
main = do
  print (mkPayment 50000 "INR")   -- Right (Payment {amount = 50000, currency = "INR"})
  print (mkPayment 0 "INR")       -- Left InvalidAmount
  print (mkPayment 100 "XYZ")     -- Left (UnsupportedCurrency "XYZ")
  print (totalPaise [Payment 100 "INR", Payment 250 "INR"])  -- 350

Notice what this buys you: there is no way to construct a Payment without passing validation, errors are values in the type signature instead of hidden exceptions, and amounts are integers in paise. The compiler forces every caller to handle the Left case. This is the practical argument for functional programming in payments, and being able to articulate it in your culture round is valuable.

Concepts to be able to explain

ConceptOne-line explanationMainstream analogue
Pure functionOutput depends only on input; no side effectsA static method that touches no shared state
ImmutabilityValues never change after creationJava records, Kotlin val, final fields
Maybe / EitherAbsence or failure encoded in the typeOptional, Result types in Rust
map / filter / foldTransform collections without loopsJava streams, Python comprehensions, reduce
Type classesAd-hoc polymorphism via constraintsInterfaces, Rust traits
Algebraic data typesSum and product types with exhaustive pattern matchingSealed interfaces with switch patterns, Rust enums
IO / effects in typesSide effects are visible in signaturesNo direct equivalent; closest is explicit dependency injection

PureScript applies the same ideas to front-end and SDK code, compiling to JavaScript. Rust, used in Hyperswitch, borrows many of these ideas (enums with data, Result, Option, traits) while adding ownership for memory safety. If you have a weekend, work through the first chapters of an introductory Haskell book and write a small CLI in Haskell; mentioning a concrete thing you built is far stronger than saying "I am interested in FP".

Juspay Technical and Culture Interviews

Technical interview themes

  • Tree locking or a similar data structure design problem with complexity analysis and concurrency follow-ups.
  • Your earlier submissions: why you chose an approach, how you would change it, what edge cases you considered.
  • CS fundamentals: OS (threads, locks, deadlocks), DBMS (transactions, indexes), networking basics.
  • Payments intuition: idempotency, retries, what happens if a bank callback never arrives. The UPI payment system design post is good background reading.
  • Projects: be ready for a deep dive on anything on your resume.

Culture round

Juspay places weight on learning ability and ownership. Expect questions such as: what have you learned on your own recently and how; a time you were stuck and how you got unstuck; why Juspay specifically; how you feel about learning a language like Haskell. Honest curiosity beats polished buzzwords here.

Internship to Full-Time: How Conversion Typically Works

For freshers, Juspay commonly hires through long internships, frequently around six months, that convert into full-time roles based on performance. This is different from short summer internships at most product companies.

  1. Clear the hiring challenge and receive an internship offer with a stipend and, in some cases, a stated full-time package on conversion.
  2. Onboard and learn the stack, often including functional programming training in the early weeks.
  3. Ship real work on a product team. Reviews look at code quality, learning speed and ownership.
  4. Conversion decision near the end of the internship. Strong interns receive full-time offers.
💡
Questions to ask before acceptingHow long is the internship, is it full-time and on-site, what is the stipend, what is the full-time package on conversion, and what share of interns typically convert? Also ask how the internship timing fits with your college's final-semester rules. The PPO guide covers how to maximise conversion odds.

Juspay Salary in 2026

StageApproximate figuresNotes
InternMonthly stipend, varies by editionCheck your offer letter; stipend is separate from FTE CTC
Fresher full-time (on conversion)~18 to 30 LPA totalMay include variable pay or ESOP components
Engineer, 2 to 5 years~25 to 45 LPAWide spread depending on role and negotiation
Senior / lead~40 LPA and aboveESOPs can be a meaningful part at senior levels

Approximate 2026 ranges from public sources such as AmbitionBox, Glassdoor and levels.fyi; varies by team, location and negotiation. If an offer includes ESOPs, understand vesting, strike price and liquidity before valuing them; our ESOP guide for Indian startups explains how.

A 4-Week Juspay Preparation Plan

WeekFocusDeliverable
1Graph fundamentals: BFS, DFS, cycle detection, functional graphsSolve the three problems above from scratch in under 45 minutes total, plus 10 similar problems
2Trees and data structure designImplement tree locking from memory; LRU cache; serialize/deserialize tree
3Concurrency and fundamentalsWrite a thread-safe version of tree locking; revise OS and DBMS basics
4Functional programming and mocksWrite a small Haskell or PureScript program; two mock interviews; polish project stories

Juspay's process favours candidates who think carefully rather than fast. Slow down on the coding round, read every tie-break rule, use 64-bit sums, and in interviews narrate the races and edge cases you are guarding against. Do that, show real curiosity about functional programming, and you will stand out in a challenge that many candidates approach as just another online test.

Frequently Asked Questions

What is the Juspay hiring challenge?▾
It is Juspay's multi-stage hiring process for students and recent graduates, commonly reported to include an online coding round, a development or problem-solving round, and technical and culture interviews. Exact rounds change between editions, so read the official announcement for the cycle you apply to.
Do I need to know Haskell to get into Juspay?▾
No. Juspay commonly says it teaches functional programming on the job, and the coding rounds accept mainstream languages. Basic familiarity with immutability, pure functions and types like Maybe and Either does help you in interviews and shows genuine interest.
What are the famous Juspay coding questions?▾
Frequently reported problems involve a directed graph where each node has at most one outgoing edge: maximum weight node, nearest meeting cell and largest sum cycle. The m-ary tree lock, unlock and upgrade problem is also widely reported in interviews, often with a concurrency follow-up.
Does Juspay convert interns to full-time employees?▾
Juspay has historically used long internships, often around six months, as the main route to full-time roles for freshers, with conversion based on performance. Conversion is not automatic, so confirm the terms and duration in your internship offer.
What is the Juspay salary for freshers?▾
Approximate 2026 figures from public sources such as AmbitionBox, Glassdoor and levels.fyi suggest full-time fresher offers roughly in the 18 to 30 LPA range, with internship stipends separate. Figures vary by role, year and negotiation, so verify against your offer.
Which language should I use in the Juspay coding round?▾
Use the language you are fastest and most accurate in, typically C++, Java or Python. Juspay's problems are about clean graph reasoning and correct handling of edge cases, not language tricks.
Where is Juspay located?▾
Juspay is headquartered in Bengaluru, and most engineering roles are based there. Confirm whether a specific role is on-site, hybrid or remote with the recruiter.

Get Strong at Graph and Tree Problems

Prepflix's DSA program builds the graph, tree and concurrency reasoning that Juspay's hiring challenge and interviews test, with mock interviews for practice.

Explore the Prepflix Course →
Pranjal Jain
Pranjal Jain

Ex-Microsoft SDE · IIT Kanpur · Founder of Prepflix. Helps engineers crack startup and product company interviews across India.