Interview guides · Appzen
Appzen Software Engineer Interview Guide
Appzen Software Engineer interview guide: reported rounds, linked list, Java, JavaScript and React questions, system design and a 7-day prep plan.
The 60-second brief
Appzen builds AI-driven software for enterprise finance and expense auditing. Candidates describe a product that processes large volumes of financial data, flags anomalies and passes insights back to the customer organizations that use it. As candidates describe the Software Engineer role, it covers the systems behind that product: backend APIs in Java or Python, React user interfaces, data pipelines and microservices, built alongside product managers, data scientists, UI/UX designers and onshore and offshore engineers.
This guide is for engineers preparing for the Appzen Software Engineer loop, whether backend (Java or Python, with Spring and Hibernate named for backend work), frontend (JavaScript and React) or full stack. Candidates describe 3+ years of professional experience as typical, and 5+ years with system design experience for senior roles. The guide covers all five reported rounds, the reported data structure, language, React, system design and behavioral questions, and a seven-day plan.
Candidates report five rounds over roughly 4-6 weeks: HR Screening, an Online Assessment on HackerRank, Virtual Technical Rounds, Discussions with Leads, and an In-Person Meet-and-Greet that appears for some locations.
Focus areas
- Data Structures (DSA)
- Linked List
- React
Overview
The work candidates describe is product engineering for a finance-auditing platform: backend services that ingest and process financial data, React interfaces that present results, and the code reviews, design write-ups and production debugging around them. That asks for two kinds of preparation. One is code you can write cleanly with no framework help. The other is the ability to explain why a design holds up when data volume, upgrades or third-party systems change.
The reported questions lean on fundamentals. Candidates describe early rounds that centre on data structures, with linked lists mentioned most often, plus complexity analysis, before any Spring, Hibernate or React discussion. The question list matches that: build and reverse a singly linked list, detect a cycle, find a target sum in an array, explain how a hash map handles collisions. Practise these by writing the code by hand, in a plain editor, and stating time and space for every method.
Language and framework questions come next. The Java items are a thread-safe Singleton, multithreading with deadlock handling, and interfaces versus abstract classes. The JavaScript items are a promise-based API-call function and a React component that fetches data and renders a table. Pick the language you will interview in and be ready to explain what the runtime does, not only to produce code that runs.
The later conversations, which candidates place with technical leads and the Director of Engineering, move to your past work: the architecture of your current project and its trade-offs, keeping an application ready for JDK or framework upgrades, an integration system for large volumes of third-party financial data, a junior developer's code review, and a production performance bottleneck. Prepare real projects with real numbers from your own experience, because these answers cannot be memorised in advance.
The Appzen interview process
These are the five stages candidates describe; the exact number of rounds can shift with location and seniority.
HR Screening
Candidates describe the opening call as a recruiter conversation about your background, your salary expectations and your timeline, and some advise stating your notice period at this point. Treat it as a logistics round with a technical backdrop: the facts you give here travel with you through the loop, so have them settled before you dial in.
What to demonstrate
- How your background lines up with a Java, Python or JavaScript engineering role
- Whether your compensation expectations and timeline fit
- Your availability, including notice period
How to prepare
- Write a short career summary that names the stack you have used in production (Java, Python, JavaScript, React, Spring or Hibernate) and one project you can discuss in depth.
- Decide your compensation range, notice period and location constraints before the call, and say them plainly.
- Ask the recruiter which later rounds are technical so you can budget preparation time, and whether the final stage is in person for your location.
Online Assessment
Candidates report a HackerRank assessment that mixes coding challenges with multiple-choice questions, and some describe three coding problems. Candidates also say the first live technical round can return to a problem they skipped or only partly solved, so treat every submission as something you may have to explain and improve later.
What to demonstrate
- Working code under a clock, in the reported coding-challenge format
- Choice of data structure and the time and space of your solution
- Fundamentals in your main language for the multiple-choice part
How to prepare
- Do timed practice on linked-list and array problems of the kind reported: cycle detection and target sum are good starting points.
- Keep a copy of every solution you submit, and after the test finish any unsolved problem and write the optimal version.
- Write code in a plain editor without autocomplete, since an assessment platform offers little help, and run your own edge cases (empty input, single element, duplicates).
Virtual Technical Rounds
Candidates describe a series of technical interviews on data structures, language mechanics and framework-specific challenges. The reported question types in this area are implementing a linked list and a hash map, array problems, Java concurrency and object design, JavaScript promises and React components. A coding stage like this tests code that works and an explanation you can defend.
What to demonstrate
- Data structures built by hand, with complexity stated and defended
- Language mechanics such as thread safety, deadlock handling and asynchronous flow
- Framework skill, shown by building a small React component
How to prepare
- Implement a singly linked list, a reverse, a cycle check and a chained hash map without built-in collections, and say the complexity of each method.
- Write three thread-safe Singleton versions and a deliberate deadlock with its fix, so you can talk through the mechanics.
- Build the React table that loads asynchronous data, and write the promise-based API-call function in plain JavaScript.
Discussions with Leads
Candidates place conversations with technical leads and the Director of Engineering here, centred on past projects and system design, with scenario-based and behavioral questions. The question types in this area are an architecture walkthrough of your own project, designing for upgrades of a JDK or framework, an integration design for third-party financial data, a code review story and a production bottleneck story.
What to demonstrate
- How clearly you explain the architecture and trade-offs of work you actually did
- System design for data-heavy integrations and for staying compatible with upgrades
- Code review judgment and how you handle production problems
How to prepare
- Prepare a walkthrough of your most complex deployed project: components, data flow, two decisions with the alternatives you rejected and what each cost.
- Practise the third-party financial data integration design aloud, covering authentication, queuing, idempotent writes, retries, encryption and monitoring.
- Write STAR outlines for the junior developer code review and the production performance bottleneck, with the signal that located the problem.
In-Person Meet-and-Greet
Candidates say the final stage may be an in-person meet-and-greet at the office for some locations, aimed at team fit and collaboration style. Little technical content is reported for it, so prepare to talk about how you work with product managers, designers, data scientists and teammates in other locations.
What to demonstrate
- Team fit and collaboration style, as the reported purpose of the stage
- How you describe working with product managers, designers and onshore and offshore colleagues
How to prepare
- Prepare two or three collaboration stories where you worked across roles or time zones and what you changed as a result.
- Write questions for the team about how code review, design write-ups and releases run day to day.
- Review your resume line by line so you can discuss any project on it, especially anything involving Java, React or microservices.
Where candidates lose points
Reaching for built-in collections when asked to build a linked list or hash map
Practise the from-scratch versions: a node class, add, remove, find, reverse, and a bucket array with chaining and resizing. Test the empty list, one node and two nodes, since reverse and remove most often break on those.
Writing a Singleton that is not actually thread-safe, such as double-checked locking without volatile
Know three correct versions (eager static, initialization-on-demand holder, enum) and be able to say why a non-volatile field in double-checked locking can expose a partly built object.
Leaving an assessment problem unfinished and never returning to it
Candidates report that a live round can revisit a skipped or partial HackerRank problem. Save your submissions, finish the problem afterwards and write the optimal solution with its complexity.
Giving a project walkthrough that lists components but no trade-offs
For each major decision, name the alternative you rejected, what it would have cost and what your choice cost you. Prepare one example where the decision proved wrong and what you changed.
Designing the third-party financial data integration without security, failure handling or idempotency
Work from a checklist: authenticating to each provider, encryption in transit and at rest, a queue between ingest and processing, idempotent writes, retries with a dead-letter queue, rate limits per provider, audit logging and monitoring of lag.
Questions & practice
Implement a singly linked list from scratch and write a function to reverse it.
Medium · Data Structures and Algorithms
Approach
- Define a Node class (value, next) and a list class that holds head, tail and size. addFirst and addLast are O(1) when you keep a tail pointer; remove(value) and find(value) are O(n) and need a trailing prev pointer so you can unlink the node.
- Reverse iteratively with three pointers: prev = null, curr = head; in the loop save next = curr.next, set curr.next = prev, then advance prev = curr and curr = next. When the loop ends, head = prev. That is O(n) time and O(1) extra space.
- Swap head and tail after the reversal: the old head becomes the new tail and its next must be null. Size does not change.
- Mention the recursive version (reverse the rest, then head.next.next = head; head.next = null). It also runs in O(n), but it uses O(n) call-stack depth, so a long list can overflow the stack.
- Test the empty list, a single node, two nodes, and removing the head or the tail. Those cases are where null-pointer bugs and stale tail pointers show up.
Follow-up
- Reverse the nodes in groups of k, leaving any final group shorter than k as it is.
- Reverse only the sublist between positions m and n in one pass.
- How does reversal change for a doubly linked list, and which invariants must hold afterwards?
How would you evaluate the time and space complexity of a peer's data structure implementation?
Medium · Data Structures and Algorithms
Approach
- Go through each public operation and express its cost as a function of n. Count nested loops, and look for linear calls that hide inside a loop: list.contains, indexOf, ArrayList.remove(0), or string concatenation in a loop in Java.
- Separate worst-case, average and amortized costs. ArrayList.add is amortized O(1) because the array grows geometrically, but a single add that triggers a resize costs O(n). Hash lookups are O(1) on average and O(n) in the worst case.
- Count space separately: auxiliary memory against input size, copies of the input, and recursion depth, which uses O(depth) stack.
- Check the analysis empirically by doubling n. Time should roughly double for linear code and roughly quadruple for quadratic code. In Java, use a harness such as JMH so JIT warm-up does not distort the numbers.
- Write the review comment concretely: name the operation, its current bound, the input that triggers it, and a fix. Examples are a HashSet for membership tests or a tail pointer for appends.
Follow-up
- When is an O(n log n) or even O(n^2) implementation acceptable to approve as written?
- What is the amortized cost of a dynamic array append if capacity grows by a constant amount instead of doubling?
- How do you raise a complexity concern without blocking a merge when production inputs are small?
Write an algorithm to detect a cycle in a linked list.
Medium · Data Structures and Algorithms
Approach
- Use Floyd's tortoise and hare. slow moves one step and fast moves two. If they meet, there is a cycle. If fast or fast.next becomes null, there is none. This is O(n) time and O(1) space.
- Mention the simpler alternative: a HashSet of visited node references, compared by identity, not value. It is O(n) time and O(n) space.
- To find where the cycle starts, reset one pointer to head after the meeting and move both pointers one step at a time; they meet at the cycle entry. To get the cycle length, hold one pointer still and count steps until the other returns to it.
- Order the null checks so that fast.next is checked before fast.next.next is read. Cover an empty list, a single node pointing to itself, and a cycle that starts at the head.
Follow-up
- Prove why the reset finds the entry. Hint: if a is the distance from head to the entry, b the distance from the entry to the meeting point and c the cycle length, then a + b is a multiple of c.
- Break the cycle so the list becomes linear again.
- Use the same technique to find the duplicate in an array of n+1 integers whose values are in 1..n, without modifying the array.
Given an array of integers, write an optimized solution to find a specific target sum.
Medium · Data Structures and Algorithms
Approach
- Clarify first: a pair or a contiguous subarray, sorted or unsorted input, indices or values in the output, one answer or all of them, and whether duplicates or negatives can appear.
- For an unsorted pair, make one pass with a hash map from value to index. Check whether target - x is already in the map before inserting x, so one element is never used twice. This is O(n) time and O(n) space.
- If the input is sorted, or sorting is allowed, use two pointers from both ends: move left when the sum is too small and right when it is too large. Sorting adds O(n log n) and loses the original indices unless you carry them along.
- If the target is a contiguous subarray sum, use prefix sums with a hash map of prefix counts, which is O(n). A sliding window only works when all values are non-negative.
- Edge cases: no valid answer, duplicate values (x + x = target needs two distinct indices), and int overflow in Java, which you avoid by using long for the sum.
Follow-up
- Extend it to three numbers that sum to the target without duplicate triplets (sort plus two pointers, O(n^2)).
- Count all pairs that hit the target instead of returning the first one.
- What changes if the array is too large for memory or arrives as a stream?
Explain the underlying implementation of a Hash Map and how it handles collisions.
Medium · Data Structures and Algorithms
Approach
- Describe the core structure: an array of buckets, with index = hash(key) mapped into the table size. Java's HashMap uses power-of-two capacities, mixes the high bits in with h ^ (h >>> 16), and takes the index as hash & (capacity - 1).
- Collisions with separate chaining: each bucket holds a linked list. Since Java 8, a bucket becomes a red-black tree once its chain reaches 8 entries and the table has at least 64 slots, so a bad bucket costs O(log n) instead of O(n). Contrast this with open addressing (linear or quadratic probing, double hashing), which needs tombstones for deletes and suffers from clustering.
- Resizing: Java's default capacity is 16 and its default load factor is 0.75. Passing the threshold doubles the table and redistributes every entry, which costs O(n) once but keeps insert amortized O(1).
- Explain the equals/hashCode contract: equal keys must return equal hash codes. If a key's fields change after insertion, its bucket is wrong and lookups fail.
- HashMap is not thread-safe. ConcurrentHashMap in Java 8 and later uses CAS plus per-bin locking. Python's dict uses open addressing, which helps if Python is your main language.
Follow-up
- Implement put, get and resize for a chaining hash map from scratch.
- What happens to performance if every key's hashCode returns the same constant?
- Why does the table use power-of-two capacities, and what does that require of the hash function?
Write a thread-safe Singleton class in Java.
Medium · Core Language and Frameworks
Approach
- Start with the holder idiom: a private constructor plus a private static nested class Holder { static final Singleton INSTANCE = new Singleton(); }, with getInstance() returning Holder.INSTANCE. It is lazy and thread-safe because the JVM initializes a class exactly once, and it needs no lock.
- Know double-checked locking: check for null, enter synchronized(Singleton.class), check again, then assign. The field must be volatile, or another thread can see a non-null reference to an object that is not fully constructed, because writes can be reordered.
- Compare the options. Eager initialization through a static final field is simple but not lazy. A synchronized getInstance() is correct but takes a lock on every call. An enum with a single INSTANCE constant also resists reflection and serialization attacks.
- Name the ways a second instance can appear: reflection calling the private constructor (throw if the instance already exists), deserialization (add readResolve), and multiple classloaders.
Follow-up
- Why exactly is volatile needed in double-checked locking? What can another thread observe without it?
- How do you unit-test code that depends on a singleton? What changes if a framework such as Spring manages the instance as a bean?
- Make the singleton hold mutable state safely. Which accesses still need synchronization?
Build a React component that fetches asynchronous data and renders it inside a table.
Medium · Core Language and Frameworks
Approach
- Keep three pieces of state: data, loading and error. Fetch inside useEffect, with the parameters the request depends on in the dependency array.
- Create an AbortController in the effect and abort it in the cleanup. That avoids setting state after unmount and stops a slow stale response from overwriting a newer one. In development, React 18 StrictMode mounts, unmounts and remounts, and the cleanup handles that as well.
- Render each state explicitly: a loading indicator, an error message with a retry, an empty-table message, then the table. Build thead from a column config and map rows in tbody with a stable key such as row.id, not the array index.
- For large data, use server-side pagination or virtualize the rows (for example with react-window) so only visible rows reach the DOM. Wrap sorting and filtering in useMemo so they do not rerun on every render.
- Move the fetching into a custom hook such as useFetch(url) so the table component only renders. A caching library such as React Query is the next step if several components need the same data.
Follow-up
- Add client-side sorting when a column header is clicked. What state do you need, and where does it live?
- The endpoint returns 10,000 rows. What do you change?
- How would you test this component with a mocked fetch?
Describe the differences between interfaces and abstract classes in Java, and when you would use each.
Medium · Core Language and Frameworks
Approach
- Interfaces: a class can implement many of them. Since Java 8 they can have default and static methods, and since Java 9 private methods. Their fields are implicitly public static final, and they have no constructors or instance state.
- Abstract classes: a class can extend only one. They can have constructors, instance fields, protected and package-private members, and a mix of concrete and abstract methods.
- Use an interface for a capability or contract across unrelated types (Comparable, Runnable), and for types you inject or mock. Use an abstract class when closely related subclasses share state and code, as in the template method pattern.
- Combine them where it helps: an interface for the type plus an abstract skeletal implementation, as java.util does with List and AbstractList. Since Java 17, sealed interfaces and classes can restrict which types may implement them.
- Diamond case: if two interfaces provide default methods with the same signature, the implementing class must override the method. It can delegate with InterfaceName.super.method().
Follow-up
- How do you add a method to a published interface without breaking existing implementors?
- Why would you write an abstract class that has no abstract methods?
- How do functional interfaces relate to lambdas?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
Stop tag and share joins from fanning out a page
Medium · join fan-out · semi-joins · many-to-many
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
Write a promise-based function in JavaScript to handle API calls.
Medium · Core Language and Frameworks
Approach
- Write an async function around fetch and remember that fetch rejects only on network failure. A 404 or 500 still resolves, so check response.ok and throw an error that carries the status and body.
- Add a timeout with AbortController: start setTimeout(() => controller.abort(), ms), pass controller.signal to fetch, and clear the timer in finally.
- Retry only transient failures (network errors, 429, 5xx) with exponential backoff and jitter, and cap the number of attempts. Do not retry other 4xx responses, and do not retry a non-idempotent POST unless the server accepts an idempotency key.
- Handle the body carefully: a 204 has no JSON, and a JSON parse error should surface as a clear error. If asked to use new Promise explicitly, resolve and reject once, and do not wrap a promise you already have.
- For several calls, explain the combinators. Promise.all rejects on the first failure, Promise.allSettled returns every outcome, Promise.race settles with the first result, and Promise.any takes the first success.
Follow-up
- Limit the number of requests in flight to N while processing a list of URLs.
- Cancel an in-flight request when a newer one for the same resource starts.
- How would you test the retry and timeout paths without real network calls?
Walk me through the architecture of your current project and explain the trade-offs you made.
Medium · System Design and Project Experience
Approach
- Use a fixed structure. Give the context (users, load, the problem it solves), sketch the components and data flow, go deep on two or three decisions, and close with what you would change.
- For each trade-off, name the alternative you rejected and the cost you accepted. For example, a relational database for transactional consistency at the cost of schema migrations, or an async queue to decouple services at the cost of eventual consistency and idempotent consumers.
- Bring numbers you can defend: request rate, data volume, latency targets, and what you measured before and after a change.
- Say "I" for your own decisions and be explicit about which parts the team owned. Expect to be asked about any box on your diagram.
- Cover failure handling: what happens when a dependency is slow or down (timeouts, retries, fallbacks), and how you would find out (metrics, alerts).
Follow-up
- What breaks first if traffic grows ten times?
- Which decision would you reverse today, and why?
- How did you validate that the trade-off was the right one after shipping?
How do you ensure your application remains flexible to future JDK or framework upgrades?
Medium · System Design and Project Experience
Approach
- Depend only on supported public APIs. JDK internals are strongly encapsulated by default since JDK 16, and JDK 17 removed the --illegal-access escape hatch. Run jdeps --jdk-internals to find usages, and jdeprscan or -Xlint:deprecation to catch APIs marked @Deprecated(forRemoval = true).
- Put framework and vendor code behind your own interfaces or adapters, so that an upgrade touches a thin layer instead of business logic. Constructor injection keeps that seam testable.
- Upgrade continuously instead of in big jumps. Target LTS releases (11, 17, 21, 25), pin versions through a BOM in Maven or Gradle, automate dependency update PRs, compile with --release, and add the next JDK to the CI matrix early.
- Know the known breaks. JDK 11 removed the Java EE modules such as JAXB and JAX-WS. Spring Boot 3 requires Java 17 and moves javax.* packages to jakarta.*.
- Make the upgrade safe to ship: broad automated tests including contract tests, then a canary or staged rollout with a fast rollback path.
Follow-up
- Walk through a real JDK or framework upgrade you did and what broke.
- A critical library has no release for the new version. What do you do?
- What is the difference between binary and source compatibility, and why does it matter for a shared library?
Design an integration system that securely handles large volumes of third-party financial data.
Medium · System Design and Project Experience
Approach
- Scope it first: which sources (REST APIs, SFTP file drops, webhooks), volume and peak rate, batch or near-real-time freshness, data formats, and who consumes the result.
- Ingestion: run one connector per source, pulling on a schedule with saved cursors or receiving webhooks after verifying their signatures. Store each raw payload unchanged and encrypted in object storage with source, receive time and checksum, then put a message on a queue (for example Kafka or SQS).
- Processing: stateless workers validate the schema, map records to a canonical model, deduplicate on an idempotency key (source plus record id, or a file hash), and upsert. Bad records go to a dead-letter queue, and the raw store lets you replay after a fix.
- Security: TLS in transit, KMS-managed encryption at rest, connector credentials in a secrets manager with least privilege, tokenized or masked sensitive fields such as card numbers, per-tenant isolation, and an audit log of every access.
- Reliability and scale: partition work by tenant or source, respect each vendor's rate limits with backoff, apply backpressure when consumers lag, and monitor freshness, error rates and reconciliation of record counts and totals against the source.
Follow-up
- A vendor's API is down for a day. What happens to the pipeline and to downstream users?
- A vendor changes its file schema without notice. How do you detect it and limit the damage?
- How do you guarantee a transaction is never counted twice?
Explain the core concepts of multithreading and how you manage deadlocks.
Medium · Core Language and Frameworks
Approach
- Cover the basics in order. Threads share the heap and each has its own stack. A race condition is unsynchronized read-modify-write on shared state. Visibility in Java comes from happens-before edges: synchronized, volatile, Thread.start and join. For atomicity, use locks or CAS classes such as AtomicInteger.
- Name the tools you would actually use: ExecutorService thread pools rather than raw threads, ReentrantLock or synchronized, ConcurrentHashMap, and CountDownLatch or CompletableFuture for coordination.
- Deadlock needs four conditions at once: mutual exclusion, hold and wait, no preemption, and circular wait. Prevent it by breaking one of them. The usual choice is a global lock order (for example, by account id). The alternatives are tryLock with a timeout followed by backing off, or taking all the locks together.
- Diagnose it with a thread dump. jstack reports Java-level deadlocks, and ThreadMXBean.findDeadlockedThreads() can detect them at runtime. Also keep critical sections short and never call unknown code while holding a lock.
- Distinguish deadlock from livelock (threads keep retrying without progress) and from starvation (a thread never gets the lock).
Follow-up
- Write a transfer between two accounts that cannot deadlock when two threads transfer in opposite directions.
- When would you choose ReentrantLock over synchronized?
- Why must wait() be called in a loop that rechecks the condition?
Describe a scenario where you had to debug a severe performance bottleneck in production.
Medium · System Design and Project Experience
Approach
- Open with the symptom as a measured number: which metric moved (for example p99 latency or error rate), by how much, for which endpoint or users, and the business impact.
- Explain the narrowing sequence. Scope it by endpoint, host and start time. Correlate with deploys, traffic and data growth. Use tracing or APM to find the slow span, then profile: thread dumps for lock contention or pool exhaustion, CPU flame graphs, GC logs, and the database slow query log with EXPLAIN ANALYZE.
- Name the causes you ruled out and how, such as N+1 queries, a missing index, connection pool saturation, GC pressure from allocation, or an external call with no timeout. Then name the real cause with the evidence that confirmed it.
- Separate mitigation from the fix: what you did first to stop the impact (rollback, scaling, a feature flag), then the root-cause change, and the before-and-after numbers on the same metric.
- Close with prevention: the alert, dashboard, load test or regression test you added, so the same failure would be caught earlier.
Follow-up
- How did you rule out the explanations that turned out to be wrong?
- What would you have done if you could not reproduce the problem outside production?
- What monitoring existed before, and why did it not catch the problem sooner?
Suggested practice checklist
The plan starts with the data structure questions candidates report, moves through Java and JavaScript mechanics and system design, then turns your own projects into stories and finishes with a timed mock of the early rounds.
Day 1 — Linked lists from scratch
- Implement a singly linked list without built-in collections: add at head and tail, remove by value, find and size, then write an iterative reverse using previous, current and next pointers.
- Write cycle detection with slow and fast pointers, extend it to return the node where the cycle starts, and state O(n) time and O(1) space for each method.
- Test the empty list, one node, two nodes and a list whose tail points back to its head.
Deliverable: A working list class with add, remove, find, reverse and cycle detection, a test for every edge case above, and a complexity line above each method.
Day 2 — Hash maps, target sum and complexity review
- Build a hash map with separate chaining: hash to a bucket index, chain collisions, and resize and rehash when the load factor passes a threshold. Say what degrades to O(n) and what changes if buckets become balanced trees.
- Solve the target-sum problem three ways (nested loops, sort plus two pointers, one-pass hash lookup) and note time, space and how duplicates or returning indices change each.
- Write a checklist for reviewing a peer's data structure code for time and space: loops over the same input, hidden linear work such as list contains or string concatenation in a loop, and memory growth.
Deliverable: A chained hash map with a resize test, three target-sum solutions with a complexity table, and a one-page complexity review checklist.
Day 3 — Java concurrency and object design
- Write three thread-safe Singletons (eager static, initialization-on-demand holder, double-checked locking with a volatile field) and explain why volatile is needed and why an enum Singleton also survives reflection and serialization.
- Write a program that deadlocks with two locks taken in opposite order, capture it in a thread dump, then fix it twice: with a global lock order and with tryLock and a timeout.
- Fill in a comparison of interface versus abstract class (multiple type inheritance, default methods, state and constructors, evolving an API) with an example of when you would pick each.
Deliverable: Three Singleton implementations with a one-line correctness argument each, a reproduced deadlock with both fixes, and a written interface versus abstract class comparison.
Day 4 — JavaScript async and React
- Write a promise-based wrapper around fetch that rejects on non-2xx responses, supports a timeout through AbortController, and retries only idempotent calls with backoff, then rewrite it with async/await.
- Build a React table component: fetch in an effect, show loading, error and empty states, ignore stale responses on unmount, and use stable row keys.
- Add sorting and pagination (or windowing for a long list) and explain what re-renders and why, including where useMemo and React.memo help.
Deliverable: A tested API-call helper in promise and async/await forms, and a React table with loading, error, empty, sorted and paginated states.
Day 5 — System design for integrations and upgrades
- Sketch the integration system for large volumes of third-party financial data: authentication to each provider, a queue between ingest and processing, validation and normalisation, idempotent writes, retries with a dead-letter queue, encryption, audit logging and per-provider rate limits.
- List what breaks first at ten times the volume (provider quotas, consumer lag, database write throughput) and what you would monitor to see it coming.
- Write a checklist for keeping a Java service ready for a JDK or framework upgrade: run CI on the next version, avoid internal APIs, keep dependencies current, hold a compatibility test suite and upgrade in small steps.
Deliverable: One architecture diagram with a failure-mode list for the integration, and a one-page upgrade-readiness checklist.
Day 6 — Project and behavioral stories
- Prepare the architecture walkthrough of your most complex deployed project: components, data flow, two decisions with the alternatives you rejected and what each cost.
- Write STAR outlines for the junior developer code review and for the production performance bottleneck, naming the metrics, the profiler, query plan or trace that located the problem, and the before and after.
Deliverable: A project walkthrough outline, two STAR outlines with measured results, and spoken answers to both practice prompts.
Day 7 — Timed mock of the early rounds
- Run a timed set in a plain editor: one linked-list problem, one array problem and one hash-map problem, then finish and optimise whichever you did worst.
- Say your recruiter-call answers aloud: career summary, notice period, compensation range and timeline.
- Go through your resume line by line, list a question to ask the team, and note the two projects you will lead with.
Deliverable: Three timed solutions with complexity notes, a written recruiter-call script, and a resume with talking points marked on every project.
Behavioural preparation
Behavioral questions in this loop, which candidates place mostly with technical leads and the Director of Engineering, are tied to engineering practice: reviewing a junior developer's code, debugging production performance, and explaining the trade-offs in a project you built. Prepare stories where you made the decision and can show the evidence.
- Describe a scenario where you had to debug a severe performance bottleneck in production.
- Walk me through the architecture of your current project and explain the trade-offs you made.
- How do you ensure your application remains flexible to future JDK or framework upgrades?
- Describe a disagreement with a product manager or designer about scope, and how you resolved it.
Tell me about a time you had to review a junior developer's code. What specific metrics do you use to evaluate
Medium · System Design and Project Experience
Tell me about a time you had to review a junior developer's code. What specific metrics do you use to evaluate code quality?
Approach
- Pick a review where you were responsible for both the outcome and the junior engineer's growth. Give the change in two sentences (what the pull request did, how large it was) and spend the rest on what you checked and why.
- Name metrics and tie each to a signal: correctness (tests on the changed paths and edge cases), time and space complexity of the new code (a nested scan on a hot path), readability (naming, function length, cyclomatic complexity), security (input validation, secrets, injection) and compatibility (reliance on deprecated or internal APIs that break on upgrade).
- Say how you treat numbers. Coverage percentage, change size, review turnaround and defect rate start a conversation; they are not targets, because a coverage figure can be met by tests that assert nothing.
- Show the mentoring: how you phrased comments (the question, the reasoning, an alternative), what you asked them to fix versus what you fixed yourself, and one concrete improvement in their later pull requests. A story that is only style nits, or one where you rewrote their code, sinks the answer.
- Close with a result you can check: the complexity fix, the bug caught before merge, or the review checklist item you added for the team.
Follow-up
- What do you do when the junior engineer disagrees and has a benchmark that supports their version?
- Which of these metrics can run automatically in CI, and which need a human reader?
- How do you review a large pull request that touches code you do not know well?
Estimate work you have never done and defend the range
Hard · estimation · backfills · expand-contract
You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.
Approach
- Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
- Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
- Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
- Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.
Follow-up
- How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
- Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?
Tell callers you do not own that their integration breaks
Medium · deprecation · compatibility · stakeholders
A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.
Approach
- Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
- Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
- Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
- Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.
Follow-up
- How would you detect a consumer that reads the field only during a monthly export?
- One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?