Interview guides · Bravura Solutions
Bravura Solutions Software Engineer Interview Guide
Bravura Solutions Software Engineer interview guide: five reported rounds, Core Java, SQL and algorithm questions, and a seven-day prep plan.
The 60-second brief
Bravura Solutions builds financial technology platforms and delivers them to clients in many countries. Those platforms handle financial data and transactions, which is why candidates describe the Software Engineer job as careful engineering on software that already exists: maintaining it, extending it, tuning how it caches data, tightening its database queries and implementing business rules. In practice that means reading unfamiliar Java code, working out why a query or a cache is slow, and changing behaviour without breaking what clients rely on. Candidates also describe regular contact with senior engineers and architects on design, and with product managers and business analysts who hand over financial requirements that you turn into technical specifications.
This guide is for candidates interviewing for the Software Engineer role at Bravura Solutions. It covers the five rounds candidates report, from Application Review to Final Decision-Making, and the thirteen questions reported for the loop: Java foundations, SQL, and algorithms and puzzles. The role lists Core Java, SQL, OOP concepts and standard algorithms as must-haves, so the guide spends most of its depth there.
Candidates report five rounds over roughly four to six weeks: Application Review, Screening Phase, Technical Assessments, Managerial Assessments and Final Decision-Making. The pace is reported to differ between immediate joiners and campus recruitment drives.
Focus areas
- Java
- Core Java OOP
- Sorting Algorithms
Overview
The Software Engineer role is described as work inside established financial platforms rather than greenfield building. The listed must-haves are Core Java, SQL and database management, OOP concepts applied to enterprise software, and the ability to implement standard algorithms and data structures. Spring Boot, microservices, SOAP or web services, JUnit and prior financial services or fintech work are listed as nice-to-haves. For preparation, that ordering matters: fundamentals first, framework familiarity second. You can recover from not having used SOAP; it is much harder to recover from not being able to explain how a HashMap resolves collisions.
The thirteen reported questions fall into three groups. The Java group is mostly conceptual: Comparable versus Comparator, how String works and why it is immutable, HashMap internals with collision handling, static versus dynamic memory allocation, and the role of static and interface implementations. The SQL group mixes one writing task (create a table with specific constraints) with three explain-and-differentiate questions on DDL, DML and DCL, join types, and primary, candidate and composite keys. The algorithm group is hands-on: implement common sorting algorithms such as Quick Sort and give their complexity, reverse a string without extra space, print the frequency of every number in an array, and solve logic puzzles such as the Water Jug problem. So the right preparation is two skills at once: explaining a concept with its reason, and producing short, correct code.
Candidates also report evaluation areas that go past those thirteen questions: memory management and finding memory leaks, multithreading, Java 8 features such as method references, the difference between final, finally and finalize, query optimization, normalization and key constraints, PL/SQL where the team uses it, transactions and locking under high concurrency, and a three-table join. Candidates report being asked to write code without an IDE and to talk through their thinking while solving puzzles. Practise by hand and out loud, not only in an editor.
Use the plan at the bottom of the guide in order. Days 2 and 3 cover the Java questions, day 4 the SQL ones, day 5 the algorithms, day 6 transactions, concurrency and debugging, and day 7 a mock round plus the project and motivation stories that the managerial stage is reported to draw on.
The Bravura Solutions interview process
Candidates report five stages across about four to six weeks. The descriptions below say what each stage is reported to cover and how to prepare, without assigning specific questions to specific rounds.
Application Review
Candidates describe this first stage as a review of the application to judge qualifications and fit, so your CV carries the whole stage. The role lists Core Java, SQL, OOP concepts and standard algorithms as must-haves, and Spring Boot, microservices, SOAP or web services, JUnit and financial services experience as nice-to-haves. Make each of those that you have really used visible together with the project it came from, because candidates also report that any project you list can become a discussion topic in later rounds.
What to demonstrate
- Whether your qualifications match the listed must-have skills: Core Java, SQL, OOP, data structures and algorithms
- Fit with the role, as judged from the application
- The projects on your CV, which candidates report are discussed in depth later
How to prepare
- Put Core Java and SQL work first and name any nice-to-have items you have genuinely used: Spring Boot, microservices, SOAP, JUnit, finance or fintech.
- For every project, write three sentences of architecture, one real challenge and your own contribution before anyone asks.
- Read the company's annual report and note the financial products it describes, so your reason for applying is specific.
- Know your availability and notice period, since candidates report the pace differs for immediate joiners and campus drives.
Screening Phase
Reported as an initial screen of basic skills and suitability. Candidates also report that early rounds lean toward breadth, meaning aptitude and core concepts, before later rounds go deep. Expect Java, OOP and SQL fundamentals plus short logic or coding exercises. Practise giving each concept as a definition, the reason behind it and a tiny example, so that breadth does not turn into shallow answers.
What to demonstrate
- Basic skills and suitability, as the stage is reported
- Breadth across aptitude and core concepts, which candidates report for early rounds
- Clear explanation of fundamentals, since communication is reported as part of the evaluation
How to prepare
- Rehearse Java and OOP fundamentals as definition, reason and small code example; days 2 and 3 of the plan cover the reported Java questions.
- Review the SQL basics in day 4: command categories, join types and key types, each with a table you can sketch from memory.
- Solve one logic puzzle and one small array or string task aloud, so the aptitude-style material is not the first time you speak your reasoning.
Technical Assessments
Candidates describe a series of technical interviews on coding skills and architectural concepts. They also report that code may have to be written without an IDE, that working and efficient code matters, and that puzzles are solved while thinking aloud. This is the stage where the coding questions of this guide (sorting, in-place string reversal, frequency counting, the Water Jug puzzle) and the Java and SQL depth topics are practised. Aim to explain why a choice is right in terms of memory and Big O, not only to produce output.
What to demonstrate
- Coding skill: code that runs correctly and efficiently
- Architectural concepts, as the stage is reported
- The Java and SQL depth that candidates report as the main evaluation areas
- How clearly you reason through a puzzle or problem
How to prepare
- Write Quick Sort, an in-place reversal of a character array, a HashMap frequency counter and a Water Jug solver by hand, then trace each on an empty input and on duplicates.
- Prepare reasons, not just answers: for each solution say its time complexity, its extra memory and the case where it degrades.
- Prepare a short architecture walk-through of one past project covering components, data flow, and where caching and database queries sit.
- Practise narrating your thinking on a puzzle you have not seen, including the point where you are stuck.
Managerial Assessments
Reported as interviews with management on cultural fit and teamwork. Candidates report that past projects are a main subject and that you should be ready to explain why you want to join Bravura Solutions and why you are leaving your current role. The role as described involves working with senior engineers, architects, product managers and business analysts, and handling production issues, so stories about that kind of collaboration are the natural material.
What to demonstrate
- Cultural fit and teamwork, as the stage is reported
- Your account of your own role and contribution in past projects
- Your reasons for wanting this company and for leaving your current role
How to prepare
- Write three project stories with a clear owner (you), a problem, a decision and an outcome; day 7 of the plan walks through this.
- Prepare one story about turning a business requirement into a technical specification with a non-engineer, and one about debugging a production issue.
- Write honest, specific answers to why Bravura Solutions and why leave your current role, using what you learned from the annual report.
Final Decision-Making
Reported as the final review and decision, made on how the interviews went. Candidates describe no further exercise for this stage, so the useful preparation happens earlier: after every round, record what you were asked and which answers you would improve, then close those gaps before you hear back.
What to demonstrate
- Overall interview performance across the earlier rounds, as the stage is reported
- The final decision on the application
How to prepare
- After each earlier round, write down every question you were asked and mark the answers that were weak; revisit those topics the same day.
- Keep your availability, notice period and location constraints ready for any follow-up conversation.
- Prepare two questions about the codebase and systems you would maintain first, so a closing conversation is informed.
Where candidates lose points
Describing HashMap or String in one line ("it uses hashing", "it is final") without the mechanism
Give the mechanism and the reason. For HashMap: a bucket array, an index derived from the key's hash, equals and hashCode deciding identity, chaining in each bucket, conversion of long chains to trees in Java 8 and later, and a resize when the load factor is exceeded. For String: the string pool, cached hash codes, thread safety and use as a map key. Rehearse each as mechanism, reason, small code example.
Trying to reverse a Java String in place, or claiming O(1) extra space with a StringBuilder
State that a Java String is immutable, so any reversal copies it. Say the truthful version: convert to a char array and swap with two pointers, which is O(n) space in Java but in place on the array, and O(1) extra beyond that. Handle empty, single-character and odd-length inputs, and mention surrogate pairs if asked about Unicode.
Writing a sorting algorithm on paper with a broken partition or no complexity discussion
Practise Quick Sort without an IDE until the partition loop is automatic. State average O(n log n), worst case O(n squared) on already sorted input with a poor pivot, in-place with a stack of O(log n), and not stable. Contrast with merge sort (stable, O(n) extra space) and name a pivot strategy such as random or median-of-three.
Knowing join names or key definitions but not what happens to rows
Practise on real tables. Show that a LEFT JOIN keeps unmatched left rows with NULLs, that a one-to-many join multiplies rows before aggregation, and that a composite key is a primary key made of several columns whose combination is unique. Write the CREATE TABLE with primary, foreign, unique, NOT NULL and CHECK constraints from memory, since writing one is a reported question.
Staying quiet on a puzzle like Water Jug or giving a vague project story
Candidates report that thinking aloud and project depth matter. For the puzzle, say you are modelling states as pairs of jug levels, list the six moves, and search breadth-first; add that a target is reachable only if it fits the larger jug and is a multiple of the gcd. For projects, bring the architecture, one challenge and what you personally did.
Questions & practice
How does the String class work, and why are strings in Java immutable?
Medium · Core Java and Technical Foundations
Approach
- Describe the class. String is final and wraps a private final array: char[] up to Java 8, and since Java 9 a byte[] plus a coder flag (compact strings, Latin-1 or UTF-16). Every 'modifying' method such as substring, concat, replace or toUpperCase returns a new String and leaves the original unchanged.
- Explain the string pool. Literals and compile-time constants are interned, so "ab" == "a" + "b" is true, while new String("ab") creates a separate heap object and == between the two is false. Compare contents with equals(). intern() returns the pooled copy, and since Java 7 the pool lives in the main heap.
- Give the reasons for immutability. Pooling is only safe if no holder can change a shared value. The hash code can be cached in a field, which makes String a cheap and safe HashMap key. Values can be shared across threads without locking. A validated value such as a file path, URL or class name cannot be altered after the check.
- Name the cost. Concatenating with += in a loop copies the accumulated characters each time, which is O(n^2) work overall, so use StringBuilder (unsynchronized) or StringBuffer (synchronized). This is also why a char[] is preferred for passwords: you can overwrite it, but a String stays in memory until it is garbage-collected.
Follow-up
- With String s = "a"; what does (s + "b") == "ab" return, and why is it different from the all-literal case?
- Can reflection change a String's contents, and what would that break?
- When would you choose StringBuffer over StringBuilder?
What is the difference between static and dynamic memory allocation?
Medium · Core Java and Technical Foundations
Approach
- Define both terms. Static allocation has a size and lifetime fixed before the program runs or when it loads: globals, static variables, fixed-size arrays in C. Dynamic allocation happens at runtime with a size that can depend on input: malloc/free in C, new/delete in C++, new in Java. Name stack (automatic) allocation of locals separately, since many people blur it with static.
- Map the terms onto Java. Every object and array created with new lives on the heap and is reclaimed by the garbage collector once it becomes unreachable; there is no explicit free. Local primitives and references live in the calling thread's stack frame. Static fields belong to the class, and since Java 8 class metadata sits in Metaspace, which replaced PermGen.
- Give the trade-offs. Stack and static allocation is cheap and its lifetime is tied to scope or the program. Heap allocation is flexible but costs allocator and GC time, and in C it can fragment memory or leak if free is missed. The typical failures are StackOverflowError from deep recursion and OutOfMemoryError: Java heap space.
- Link this to leaks in a garbage-collected language. A leak happens when a reference stays reachable: a static collection that only grows, listeners that are never removed, ThreadLocal values on pooled threads, or caches without eviction. To diagnose one, capture a heap dump (-XX:+HeapDumpOnOutOfMemoryError or jmap) and follow the dominator tree in a tool such as Eclipse MAT.
Follow-up
- In Object o = new Object(); inside a method, where does o live and where does the object live?
- How can a Java program leak memory if the garbage collector frees unreachable objects?
- How would you find what is holding memory in a service whose heap keeps growing?
Implement common sorting algorithms (e.g., Quick Sort) and explain their time complexity.
Medium · Algorithms and Problem Solving
Approach
- Write quicksort with a Lomuto partition: pivot = a[hi]; i = lo; for j from lo to hi-1, if a[j] < pivot then swap a[i], a[j] and i++; finally swap a[i], a[hi] and recurse on [lo, i-1] and [i+1, hi]. Quicksort sorts in place and is not stable. Hoare partitioning does fewer swaps.
- State the complexity precisely. Quicksort averages O(n log n). The worst case is O(n^2) when the pivot is always the minimum or maximum, which happens on already-sorted input with a first- or last-element pivot, and on all-equal keys with a plain two-way partition. The fixes are a random or median-of-three pivot and a three-way (Dutch national flag) partition for duplicates. Recursing on the smaller side first bounds the stack at O(log n).
- Compare the other standard sorts. Merge sort is O(n log n) in every case and stable, but needs O(n) extra space; it suits linked lists and external sorting. Heap sort is O(n log n) worst case with O(1) extra space, but it is not stable and has poor cache locality. Insertion sort is O(n^2) in general but O(n) on nearly sorted input, which is why hybrid sorts use it for small subarrays.
- Connect this to Java. Arrays.sort(int[]) uses Dual-Pivot Quicksort, because stability is meaningless for primitives. Arrays.sort(Object[]), List.sort and Collections.sort use TimSort, which is stable, O(n log n) worst case and close to O(n) on input that is already partly sorted.
- Mention the lower bound. Any comparison sort needs Ω(n log n) comparisons in the worst case. Counting sort and radix sort avoid that bound for bounded integer keys, at O(n + k) and O(d(n + k)) respectively.
Follow-up
- Why does Java sort int[] and Object[] with different algorithms?
- How would you find the k-th smallest element in average O(n) time?
- How would you sort a file too large to fit in memory?
Reverse a string without using extra space.
Medium · Algorithms and Problem Solving
Approach
- Clarify the input first. A Java String is immutable, so a true in-place reversal needs a char[]. toCharArray() already copies, and any method that returns a new String must allocate. State your assumption: either the input is a char[], or O(1) extra space means no buffer beyond the output.
- Use two pointers: i = 0, j = n - 1, and while i < j, swap s[i] and s[j], then i++ and j--. That is O(n) time with n/2 swaps and O(1) extra space. Use a temporary variable for the swap. The XOR swap trick saves nothing and zeroes the value if both indexes ever point at the same element.
- Cover the edge cases: null, an empty array, length 1, and odd lengths, where the middle character stays in place. Note that a recursive reversal is not O(1) space, because it uses O(n) stack.
- Raise Unicode. Reversing char by char splits surrogate pairs such as emoji. StringBuilder.reverse() treats surrogate pairs as single units, but combining marks still end up detached. For user-facing text, reverse by code point or by grapheme cluster instead.
Follow-up
- Reverse the order of words in a sentence in place.
- Check whether a string is a palindrome, ignoring case and non-alphanumeric characters, with O(1) extra space.
- What does your char-swapping version do to a string that contains an emoji?
Given an array, write a program to print the frequency of every number.
Medium · Algorithms and Problem Solving
Approach
- Start from the brute force: for each element, count matches with an inner loop and skip values already printed. That is O(n^2) time.
- Write the standard answer: one pass into a HashMap<Integer, Integer> with counts.merge(x, 1, Integer::sum), then print each entry. That is O(n) average time and O(k) space for k distinct values. Use a LinkedHashMap to print in order of first appearance, or a TreeMap for sorted output at O(n log k).
- Offer alternatives for other constraints. Sort a copy, then count runs of equal values: O(n log n) time and no map, and sorting in place mutates the caller's array. If values are known to lie in [0, m), a counting array gives O(n + m) time. Negative values need an offset.
- Show the stream version and its types. Arrays.stream(arr).boxed().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) returns a Map<Integer, Long>, and its iteration order is not guaranteed.
- Cover the edge cases: an empty array, a single element, every value distinct, every value equal, and negative or very large values. These last two rule out the counting-array approach.
Follow-up
- Print only the k most frequent values. Compare a size-k heap with bucket sort.
- How would you count frequencies over a stream too large to keep in memory?
- Can you do it with O(1) extra space if every value lies between 1 and n?
Solve logical puzzles or algorithmic challenges like the Water Jug problem.
Medium · Algorithms and Problem Solving
Approach
- Model the puzzle as a graph search. A state is (a, b), the litres in jugs of capacity x and y. The six moves are: fill either jug, empty either jug, and pour from one into the other until the source is empty or the target is full, transferring min(a, y - b) litres one way and min(b, x - a) the other. Restate what counts as the goal: the target amount in one jug, or the total across both.
- Run BFS from (0, 0) with a visited set. Every move costs one step, so the first time BFS reaches a goal state it has found the minimum number of moves. There are at most (x + 1)(y + 1) states, so the search is O(xy) time and space. Store a parent pointer per state to print the sequence of moves.
- Give the number-theory check. Measuring z as the total in both jugs is possible if and only if z ≤ x + y and z is a multiple of gcd(x, y), by Bézout's identity. To have z in a single jug, you also need z ≤ max(x, y). z = 0 is trivially reachable.
- Trace the classic case of measuring 4 with jugs of 3 and 5, writing states as (jug3, jug5): (0,5) → (3,2) → (0,2) → (2,0) → (2,5) → (3,4). That is six moves. Narrating the states out loud is how to approach any logic puzzle: define the state, the moves and the goal, then search.
- Cover the edge cases: a capacity of zero, z greater than x + y, and z equal to x or y, which takes a single fill.
Follow-up
- Why is gcd(x, y) dividing z both necessary and sufficient?
- Generalise the search to three jugs, for example splitting 8 litres into 4 and 4 with jugs of 8, 5 and 3.
- Would DFS also find a solution? Would it find the shortest one?
Write a SQL query to create a table with specific constraints.
Medium · Database and SQL Proficiency
Approach
- Pick a concrete schema and write it fully, for example: CREATE TABLE account_txn (txn_id BIGINT PRIMARY KEY, account_id BIGINT NOT NULL, amount DECIMAL(18,2) NOT NULL, currency CHAR(3) NOT NULL, txn_ref VARCHAR(40) NOT NULL, status VARCHAR(10) DEFAULT 'PENDING' NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, ...). Use DECIMAL/NUMERIC for money, never FLOAT.
- Add named table-level constraints so error messages are readable and the constraints can be dropped later: CONSTRAINT uq_txn_ref UNIQUE (txn_ref), CONSTRAINT chk_amount CHECK (amount <> 0), CONSTRAINT chk_status CHECK (status IN ('PENDING','POSTED','REVERSED')), CONSTRAINT fk_txn_account FOREIGN KEY (account_id) REFERENCES account(account_id) ON DELETE RESTRICT.
- Explain what each constraint enforces and where it falls short. PRIMARY KEY means unique and not null and creates a unique index. UNIQUE allows NULLs: most databases allow several, but SQL Server allows only one. CHECK sees only the current row. A foreign key must reference a primary or unique key.
- Point out dialect differences. Auto-generated ids are GENERATED ... AS IDENTITY in standard SQL, PostgreSQL and Oracle 12c+, IDENTITY(1,1) in SQL Server and AUTO_INCREMENT in MySQL. MySQL enforces CHECK constraints only from 8.0.16. PostgreSQL and Oracle do not index foreign key columns automatically, so add that index yourself.
Follow-up
- What happens to child rows when the parent account is deleted under ON DELETE CASCADE, SET NULL and RESTRICT?
- How would you add a NOT NULL column with a constraint to a large table that already has rows?
- Why should the foreign key column usually get its own index?
Denormalise tenant onto revisions and backfill it live
Hard · denormalisation · online migration · backfill
resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.
Approach
- Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
- Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
- Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
- Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.
Follow-up
- The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
- How do you verify the backfill actually finished, given rows are still being inserted while it runs?
Explain the difference between Comparable and Comparator.
Medium · Core Java and Technical Foundations
Approach
- Place each one. Comparable<T> (java.lang) has compareTo(T o) and lives inside the class, so it defines the single natural ordering that Collections.sort(list), Arrays.sort(Object[]), TreeMap and TreeSet use when no comparator is given. Comparator<T> (java.util) has compare(a, b) and lives outside the class, so one type can have many orderings, including types you cannot edit.
- State the contract: the sign of the result is what matters, sgn(x.compareTo(y)) == -sgn(y.compareTo(x)), and the ordering must be transitive. Never return a - b for ints, because it overflows for large values of opposite sign. Use Integer.compare(a, b) or Long.compare.
- Show the Java 8 style: Comparator.comparing(Trade::getDate).reversed().thenComparing(Trade::getAmount), with Comparator.nullsLast(...) for nullable fields. Mention that List.sort and Collections.sort on objects are stable (TimSort), which is what lets chained or repeated sorts keep earlier order on ties.
- Explain consistency with equals. TreeSet and TreeMap treat compare == 0 as the same key. BigDecimal 1.0 and 1.00 are not equal() but compareTo returns 0, so a HashSet keeps both values and a TreeSet keeps one.
Follow-up
- Write a comparator that sorts transactions by date descending, then by amount ascending, with null dates last.
- What goes wrong in a TreeSet whose comparator is inconsistent with equals?
- Why does returning a.id - b.id from compare sometimes give a wrong order?
Describe the internal mechanics of a HashMap, including collision handling.
Medium · Core Java and Technical Foundations
Approach
- Structure: an array of buckets (Node<K,V>[] table) whose capacity is a power of two, 16 by default, with a load factor of 0.75. The hash is spread as h ^ (h >>> 16) and the index is (n - 1) & hash, which only works as a modulo because n is a power of two.
- Walk through put. Compute the hash and find the bucket. If it is empty, insert. Otherwise walk the bin, comparing the hash first and then equals(), and replace the value if the key is found or append a new node if not. get follows the same path. One null key is allowed and always goes to bucket 0.
- Collisions: separate chaining. Since Java 8 a bin becomes a red-black tree once it holds 8 nodes (TREEIFY_THRESHOLD), but only if the table has at least 64 buckets; below that the map resizes instead. A tree bin turns back into a list at 6 nodes. Lookups are O(1) on average. The worst case is O(n) with plain chains and O(log n) with tree bins.
- Resize: when size exceeds capacity times the load factor, the table doubles. Each entry either stays at its index or moves to index + oldCapacity, depending on one hash bit. A single resize is O(n), but puts stay amortized O(1). Presizing the map avoids repeated resizes when the size is known.
- Contract and concurrency: equal keys must have equal hashCodes, and a key mutated after insertion is lost in the wrong bucket. HashMap is not thread-safe. Use ConcurrentHashMap, which rejects null keys and values, rather than Hashtable or Collections.synchronizedMap when contention matters.
Follow-up
- What happens if you change a field used in hashCode() after the key has been inserted?
- Why does HashMap need a power-of-two capacity, and what does the h ^ (h >>> 16) step protect against?
- How does ConcurrentHashMap make put thread-safe without locking the whole table?
Explain the role of static keywords and interface implementations in Java.
Medium · Core Java and Technical Foundations
Approach
- Cover static members. A static field has one copy per class (per class loader), shared by all instances. A static method has no this, so it cannot touch instance members directly, and it cannot be overridden: a subclass method with the same signature hides it, and the call resolves on the compile-time type. A static initializer block runs once when the class is initialized.
- Cover static nested classes. A static nested class holds no reference to an outer instance. A non-static inner class does hold one, which can keep a large outer object alive. Mention static imports and the usual uses: constants (static final), factory methods and stateless utility methods.
- Cover interfaces. A class can implement several interfaces. Abstract methods are implicitly public abstract and fields implicitly public static final. Java 8 added default and static methods; static interface methods are called as InterfaceName.method() and are not inherited by implementing classes. Java 9 added private interface methods. An interface with one abstract method is a functional interface, which a lambda or method reference can implement.
- Handle the diamond case. If a class inherits the same default method from two interfaces, it must override that method, and it can delegate with A.super.method(). Compare interfaces with abstract classes: an abstract class can hold instance state and constructors but allows single inheritance only, while an interface describes a capability any class can add.
- Name the pitfalls. Mutable static state is shared by every thread and every request, so it needs synchronization or should be avoided. Static calls are hard to replace in unit tests (JUnit with mocks) compared with an injected interface implementation.
Follow-up
- Can you override a static method? What does Parent p = new Child(); p.staticMethod() call?
- Now that interfaces have default methods, when would you still pick an abstract class?
- Why might a non-static inner class cause a memory leak where a static nested class would not?
Explain the difference between DDL, DML, and DCL commands.
Medium · Database and SQL Proficiency
Approach
- Define each group with examples. DDL (Data Definition Language) changes structure: CREATE, ALTER, DROP, TRUNCATE, RENAME. DML (Data Manipulation Language) changes rows: INSERT, UPDATE, DELETE, MERGE, and SELECT, which some texts file separately as DQL. DCL (Data Control Language) manages privileges: GRANT and REVOKE. Mention TCL (Transaction Control Language) as well: COMMIT, ROLLBACK, SAVEPOINT.
- Explain how each interacts with transactions. DML runs inside a transaction and can be rolled back. In Oracle and MySQL a DDL statement commits implicitly before and after it runs, so it also commits any pending DML and cannot be rolled back. PostgreSQL and SQL Server support transactional DDL.
- Use TRUNCATE vs DELETE as the worked contrast. DELETE is DML: it can take a WHERE clause, logs each row and fires row-level DELETE triggers. TRUNCATE is DDL: it removes every row by deallocating storage, takes no WHERE clause, does not fire DELETE triggers and resets identity counters in SQL Server and MySQL. It can be rolled back inside a transaction in PostgreSQL and SQL Server, but not in Oracle.
- Give a DCL example with least privilege: GRANT SELECT, INSERT, UPDATE ON account_txn TO app_role; REVOKE DELETE ON account_txn FROM app_role. The application account gets DML only; DDL stays with a separate migration account.
Follow-up
- Why can a TRUNCATE not be rolled back in Oracle when a DELETE can?
- Which category is MERGE in, and which is SELECT?
- How would you give a reporting user read-only access to one schema?
What are the different types of joins, and when should you use them?
Medium · Database and SQL Proficiency
Approach
- List the joins with what they return. INNER JOIN returns only matching rows. LEFT OUTER JOIN returns every left row, with NULLs where nothing matches on the right; RIGHT JOIN is the mirror. FULL OUTER JOIN returns all rows from both sides (MySQL lacks it, so emulate it with a LEFT JOIN UNION a RIGHT JOIN). CROSS JOIN returns the m x n Cartesian product. A self join joins a table to itself through aliases, for example employee to manager.
- Show the anti-join: rows with no match. Write LEFT JOIN t ON ... WHERE t.key IS NULL, or NOT EXISTS. Warn that NOT IN (subquery) returns no rows at all if the subquery yields a NULL.
- Name the two classic bugs. First, filtering on the right table in WHERE (WHERE t.status = 'POSTED') discards the NULL rows and turns a LEFT JOIN into an inner join, so that condition belongs in the ON clause. Second, joining two one-to-many tables before aggregating multiplies rows and inflates SUMs, so aggregate each child table in a subquery first.
- Practise a three-table query, for example customer to account to transaction: total posted amount per customer, including customers with none. Join from the customer grain outward with LEFT JOINs, use COALESCE(SUM(...), 0), and GROUP BY the customer key.
- Mention physical join strategies. A nested loop is good for a small outer input with an index on the inner join key. A hash join suits large unsorted equi-joins. A merge join suits inputs already sorted on the key. Indexing the join columns is what makes the nested loop option cheap.
Follow-up
- Write a query that lists customers with no transactions in the last 30 days.
- Your LEFT JOIN returns more rows than the left table has. Why, and how do you fix it?
- How does moving a filter from WHERE to ON change a LEFT JOIN's result?
Define and differentiate between primary, candidate, and composite keys.
Medium · Database and SQL Proficiency
Approach
- Build the definitions from the bottom up. A super key is any set of columns that uniquely identifies a row. A candidate key is a minimal super key: remove any column and it stops being unique. The primary key is the one candidate key chosen as the row's identifier; it must be unique and NOT NULL, and a table has only one. The remaining candidates are alternate keys.
- Use one worked example. In employee(emp_id, national_id, email, name), emp_id, national_id and email are each candidate keys. {emp_id, email} is a super key but not a candidate, because emp_id alone is already unique. Choose emp_id as the primary key and enforce the others with UNIQUE NOT NULL.
- Define a composite key: a key made of two or more columns where no subset is unique on its own. Examples are (account_id, statement_month) or a junction table's (student_id, course_id). A composite key can be the primary key or just a candidate key. Column order matters for its index: an index on (a, b) serves filters on a, or on a and b, but generally not on b alone.
- Compare natural and surrogate keys. Natural keys (an email, an ISIN) carry business meaning but can change or be reused. A surrogate key (identity, sequence or UUID) stays stable and keeps foreign keys narrow. If you use a surrogate, keep a UNIQUE constraint on the natural candidate key, or duplicates slip in. A foreign key must reference a primary key or a unique key.
Follow-up
- Can a primary key column hold NULL? Can a UNIQUE column?
- When would you pick a composite natural primary key over a surrogate id?
- Does an index on (account_id, statement_month) help a query that filters only on statement_month?
One log partition stops advancing while the others drain
Easy · poison message · head-of-line blocking · consumer lag
Search results for a subset of tenants are hours stale; the rest are current. The projection consumer reports lag of zero on 15 of 16 partitions and 400,000 on one. Its error rate is flat and its CPU is idle. outbox_event has no pending rows older than a second, so the relay has published everything it holds. Identify the mechanism, give the ordered checks, and state what you do in the first ten minutes versus what you change permanently.
Approach
- Read the lag distribution first. A slow consumer lags everywhere; zero on fifteen partitions and 400,000 on one is not throughput. Idle CPU on the stuck partition means the consumer is not advancing its offset at all, which points at one message it cannot get past rather than at a rate problem.
- Exonerate the producer before touching the consumer. No pending outbox rows older than a second means the relay published, so the event exists in the log. This separates never sent from sent and never applied, which are different code paths and usually different owners.
- Read the message at the stuck offset and the handler's log lines for its event_id. A flat error rate with no progress has two explanations and you must distinguish them: the handler is throwing and the retry loop is swallowing it, or the handler is blocking on something and never returning. Idle CPU with no error lines favours the second.
- Mitigate before diagnosing further. Move the offending event to a dead-letter store and commit the offset past it. Adding consumers does nothing here, because a partition is consumed by exactly one member of the group, and the blast radius is every aggregate hashed to that partition, not only the aggregate that produced the bad event.
Follow-up
- The dead-lettered event carried aggregate_version 7 and the projection had applied 6. What must the replay do differently if 8 and 9 landed in the meantime?
- How do you show staleness to the user while the partition is behind, given the API already returns the projection's watermark?
Suggested practice checklist
The seven days follow the reported loop: project material first, then the Java, SQL and algorithm questions, then concurrency and debugging, and a mock run on day 7.
Day 1 — Map the loop and your own projects
- Write the five reported rounds in order and one line on what each is reported to cover, then mark the stage you are least ready for.
- List every project on your CV and, for each, write a three-sentence architecture summary, one challenge and your own contribution.
- Read the company's annual report and note the financial products it describes.
Deliverable: A one-page loop map, a project sheet with architecture, challenge and contribution for each CV project, and a few notes on the annual report.
Day 2 — Java collections, strings and object contracts
- Sort a list of account objects with Comparable by one field, then with Comparator.comparing(...).thenComparing(...) by two others, and write the difference in two sentences.
- Draw a HashMap from memory: bucket array, hash-to-index step, chaining, tree conversion for long chains, resize at the load factor. Write a key class with and without hashCode to show the failure.
- Explain aloud why String is immutable, covering the string pool, cached hash, thread safety and map keys, and show the effect of new String versus a literal.
Deliverable: A working Comparator demo, a labelled HashMap diagram with the broken-hashCode demo, and a spoken String immutability answer.
Day 3 — Memory allocation, static and interfaces
- Explain static versus dynamic allocation in Java terms: locals and call frames, objects on the heap, static fields living as long as the class is loaded, and what garbage collection does.
- Write an interface with default and static methods, a class implementing two interfaces that share a default method, and resolve the conflict explicitly. Add static fields, blocks and a nested class to show initialization order.
- Produce a memory leak on purpose (a static collection that only grows) and describe how you would find it, then explain final, finally and finalize and write one method reference example.
Deliverable: A one-page note on allocation and static, a compiled interface-conflict example, and a leak reproduction with a written diagnosis.
Day 4 — SQL: constraints, command types, joins and keys
- Write CREATE TABLE statements for accounts and transactions with primary key, foreign key, unique, NOT NULL, CHECK and DEFAULT, and name the primary, candidate and composite keys in your schema.
- Sort statements into DDL, DML and DCL with two examples each, and note where TRUNCATE and transaction-control statements fall.
- Load three small tables and write inner, left, right, full, cross and self joins, then the three-table join, noting row counts and NULLs for each.
Deliverable: A runnable schema script, a DDL/DML/DCL table, a file of joins with annotated row counts, and a written backfill plan.
Day 5 — Algorithms by hand
- Write Quick Sort on paper with a random pivot, then merge sort, and note average and worst time, extra space and stability for each.
- Reverse a character array in place with two pointers, and write a frequency counter three ways: HashMap, sort then scan, and counting array. State complexity and when each fits.
- Solve the Water Jug puzzle with breadth-first search over states, then add the gcd reachability check and explain why it works.
Deliverable: Handwritten Quick Sort and merge sort, three frequency counters, an in-place reverse and a Water Jug solver, each with its complexity and edge cases written beside it.
Day 6 — Transactions, concurrency and debugging
- Open two database sessions and reproduce a lost update, then fix it with a row lock and again with a version column; write down the isolation level you were using.
- Write a Java program with a shared counter that races, then fix it with synchronized, an atomic class and a thread pool, and explain what each costs.
- Write JUnit tests for the day 5 sorting and frequency functions, including empty and duplicate inputs.
Deliverable: A lock and version-column transcript, a race demo with three fixes, a JUnit test class and a written debugging sequence.
Day 7 — Mock loop and managerial stories
- Run a mock technical round: one Java concept, one SQL query and one algorithm, answered aloud and written without an IDE.
- Write and say your answers to why Bravura Solutions and why you are leaving your current role.
Deliverable: A recorded mock round with notes on weak answers, written motivation answers, and three rehearsed project stories.
Project and teamwork preparation
Candidates report that the Managerial Assessments look at cultural fit and teamwork, and that past projects are a main subject throughout the loop. The role as described has you working with senior engineers, architects, product managers and business analysts, debugging production issues, and changing software that already exists. Build stories from that kind of work, each with your own role, a decision you made and a result, and rehearse why you want to join Bravura Solutions and why you are leaving your current role.
- Why do you want to work at Bravura Solutions, and why are you looking to move from your current role?
- Walk me through the architecture of your most recent project: what you owned, the hardest problem and how you solved it.
- Tell me about a time you turned a business or financial requirement from a product manager or business analyst into a technical specification.
- Describe a production issue you debugged: how you narrowed it down, what the fix was and what you changed to stop it recurring.
- Tell me about a time you had to change a codebase you did not write and keep existing behaviour intact.
Turn a code review disagreement into a decision
Easy · code review · optimistic concurrency · communication
A colleague's change updates a row with UPDATE resource SET version = version + 1 WHERE resource_id = $1 AND version = $2 and treats an affected-row count of zero as a successful no-op. You read that as a silently lost update; they think returning 200 is friendlier to clients than returning a conflict. Describe how you have handled a review disagreement of this shape: what goes in the comment, when you leave the thread, and who decides. Then write the comment you would leave here, in under 80 words.
Approach
- Sort the disagreement before writing anything. A silently discarded write is a correctness claim about data; the choice between 409 and 412 is taste. Only the first justifies blocking a merge, and saying which one you are doing is most of the value of the comment.
- Make the claim reproducible in the comment itself with an interleaving rather than a principle: A reads version 7, B reads version 7, B commits version 8, A's predicate matches zero rows, A is told it succeeded and A's edit is gone.
- Offer the alternative with its cost attached: return 409 carrying the current version and the revision that won, so the client can re-read and re-apply. Note that automatic retry is not the fix, because a retry re-reads the winner's state and reapplies an intent formed against data that no longer exists.
- Apply an escalation rule you can state: two round trips on the thread, then a call, and the service's owner decides rather than the reviewer. A reviewer who cannot be overruled is a bottleneck with extra steps.
Follow-up
- Where would you put the test that fails if someone reintroduces the swallowed zero rowcount?
- The author says clients cannot handle a 409. How do you check whether that is true?
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?
Unblock an engineer without taking the keyboard
Easy · mentoring · leases · at-least-once
A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.
Approach
- Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
- Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
- Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
- Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.
Follow-up
- How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
- Their handler calls an external endpoint before recording that it did. What do you tell them to change first?