A campus hire heading into her Persistent Systems technical round in 2025 spent two weeks grinding LeetCode hards and walked in to find the interviewer more interested in why a HashMap in Java isn't thread-safe by default than in anything resembling a graph algorithm. That mismatch between what candidates expect and what actually gets asked shows up across dozens of reported experiences on Glassdoor, where Persistent Systems carries an interview difficulty rating of 3.31 out of 5 and a 67 percent positive experience rate across roughly 250 submitted reviews (Glassdoor, 2026). The process rewards people who can explain fundamentals out loud, not people who've memorized the fifty hardest problems on a practice site.
Persistent hires in two very different modes and both show up in candidate reports. Campus and fresher recruitment runs through an online assessment on the AMCAT platform, aptitude and core CS MCQs plus two or three coding problems, followed by one or two technical interviews and an HR round. Experienced hiring skips the aptitude test and goes straight to a resume-driven technical screen, a deeper second round scoped to the actual stack on the project (Java and Spring Boot, Go,.NET, or Salesforce depending on the req), and a final HR or managerial conversation. Several candidates describe the whole loop, online test through offer, wrapping up inside a single week (GeeksforGeeks, Persistent Systems Recruitment Process).
This page groups roughly fifty reported questions into the seven stages candidates actually describe: the online assessment, core DSA and coding, OOP and language fundamentals, databases and SQL, networking and operating systems, cloud and domain-specific technology, and the HR round. Where reports diverge by role (Java shop versus Salesforce shop versus Go-based networking product), that's called out directly rather than blended into one generic answer.
Round 1: the online assessment
For freshers and campus hires, this is the actual filter. Candidates who ace the two or three coding questions report advancing even with a mediocre aptitude score, while a clean MCQ sheet with weak coding output tends to stall (GeeksforGeeks, Freshers Interview Experience).
Easy questions
20Because MCQ sections on logical reasoning and verbal ability correlate weakly with whether someone can actually write working code under time pressure, and Persistent's assessment is trying to predict performance in the technical rounds that follow, not general test-taking ability. A candidate who scores well on quantitative aptitude but can't get a linked-list reversal to compile is a worse bet for the technical interview than one who flips that ratio.
Practically, that means spending prep time writing actual code that compiles and passes edge cases, not grinding puzzle books. The MCQ section still matters as a gate, most reports describe a minimum cutoff, but it isn't where offers get decided.
Candidates consistently describe two or three problems in the easy-to-medium range: string manipulation (remove every other vowel, replace the second occurrence of a character), array problems (find duplicates, count occurrences), and basic data structure operations like reversing a linked list or finding its middle node. Nothing here requires competitive-programming tricks, the bar is writing correct, readable code against a handful of hidden test cases in a fixed time window.
// "replace the 2nd occurrence of 'e' with '@'" style problem
public static String replaceSecondOccurrence(String s, char target, char replacement) {
int count = 0;
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (chars[i] == target) {
count++;
if (count == 2) {
chars[i] = replacement;
break;
}
}
}
return new String(chars);
}Candidate reports are inconsistent on whether negative marking applies, some describe it, others don't, which suggests it may vary by hiring cycle or campus batch rather than being a fixed, permanent rule. Given that ambiguity, the safer approach is to treat every question as if a wrong answer costs you something: skip a question you're genuinely unsure of rather than guessing blind, but don't leave easy points on the table out of excess caution.
Reversal is the classic three-pointer walk: keep a previous, current, and next reference, and at each step flip current's next pointer to point backward before advancing all three forward. Finding the middle in one pass uses the slow-fast pointer technique, advance one pointer one node at a time and a second pointer two nodes at a time, and when the fast pointer reaches the end, the slow pointer sits at the middle.
ListNode reverse(ListNode head) {
ListNode prev = null, curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
ListNode findMiddle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}Interviewers usually push further and ask you to combine the two, find the middle then reverse the second half, which is the setup for a palindrome-linked-list check.
Without a map, a 256-length integer array indexed by character code covers the ASCII case cleanly and runs in a single pass with constant extra space relative to alphabet size. The moment the input includes Unicode outside the basic ASCII range, that fixed-size array breaks down and a real hash map keyed by character becomes the only correct option.
int[] counts = new int[256];
for (char c : input.toCharArray()) {
counts[c]++;
}The follow-up question worth anticipating: what's the space-time tradeoff of the array approach versus a HashMap? The array is faster in practice due to no hashing overhead and no boxing, but it wastes space for a small alphabet and simply doesn't work for a large or unbounded character set.
Walk the string once while tracking characters already seen in a set. Append a character to the result only the first time it's encountered, and skip every repeat. This runs in linear time and uses extra space proportional to the number of distinct characters.
public static String removeDuplicates(String s) {
Set<Character> seen = new LinkedHashSet<>();
for (char c : s.toCharArray()) {
seen.add(c);
}
StringBuilder sb = new StringBuilder();
for (char c : seen) sb.append(c);
return sb.toString();
}The pattern question is not really about algorithmic difficulty. It is about whether you can cleanly reason about a nested loop's bounds without trial-and-error. The outer loop controls the row, the first inner loop prints leading spaces that shrink as rows increase, and the second inner loop prints the actual characters that grow as rows increase.
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n - i; j++) System.out.print(" ");
for (int j = 1; j <= (2 * i - 1); j++) System.out.print("*");
System.out.println();
}Where candidates actually stumble is the space count formula, they'll hardcode it for a specific n instead of deriving it in terms of the loop variable, which falls apart the moment the interviewer asks for an inverted pyramid.
Encapsulation is about bundling data with the methods that operate on it and restricting direct access to that data from outside, private fields with public getters and setters, in the simplest case. Abstraction is about hiding implementation complexity behind a simpler interface, exposing what an object does without exposing how it does it internally.
A concrete way to separate them: encapsulation is a mechanism, private fields plus access modifiers. Abstraction is a design goal, you can achieve it through an abstract class, an interface, or even just a well-named method that hides a messy fifty-line implementation. Encapsulation is one common tool for achieving abstraction, but they answer different questions.
A List preserves insertion order and allows duplicate elements, indexed access by position. A Set enforces uniqueness and, depending on the implementation, may not preserve any particular order at all. Picking a List when you actually need uniqueness means writing manual duplicate-checking logic that a Set gives you for free, and it costs you at runtime the moment that list grows large enough that a linear contains-check per insertion becomes the visible bottleneck in a profiler.
A static method belongs to the class itself rather than to any particular instance, so it can't access instance fields or call instance methods directly, there's no implicit "this" reference for it to work with, since it might be invoked without any object having been created at all. It can only reference other static members of the class, or take an object as an explicit parameter and call instance methods on that.
A wrapper class, Integer, Double, Boolean and the rest, wraps a primitive value in an object so it can be used anywhere Java requires an object, inside a generic collection, for instance, since generics can't work with raw primitives directly. Autoboxing is the compiler automatically inserting the conversion from primitive to wrapper (and unboxing, the reverse) so you don't have to write Integer.valueOf(5) by hand every time.
The hidden cost candidates rarely mention unprompted: autoboxing inside a tight loop creates a new wrapper object on every iteration unless the value falls inside the small integer cache range, which is a real, measurable source of extra garbage-collector pressure in hot code paths.
The cleanest version avoids a subquery that fails silently when there are ties, using DENSE_RANK to explicitly define what "second highest" means when duplicate salaries exist.
SELECT name, salary
FROM (
SELECT name, salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
) ranked
WHERE rnk = 2;The classic SELECT MAX(salary) FROM employees WHERE salary < (SELECT MAX(salary) FROM employees) version also works and is worth knowing, but it silently returns nothing if the two highest earners are tied at the same salary, which is exactly the kind of edge case an interviewer will push on.
A primary key uniquely identifies each row and implicitly disallows NULL values, and a table can have exactly one. A unique key also enforces uniqueness but does allow a single NULL value in most database engines, and a table can have any number of unique keys alongside its one primary key. In practice, a unique key is how you enforce uniqueness on a column, an email address, say, that isn't the row's actual identity.
TCP guarantees ordered, reliable delivery through acknowledgments and retransmission, at the cost of extra overhead and latency. UDP sends packets with no delivery guarantee and no ordering, but with far less overhead. Live video calls and multiplayer game state are the classic case for choosing UDP on purpose, a dropped or late packet describing a stale video frame or an outdated player position is worse than useless, and retransmitting it after the moment has passed only adds lag. Better to drop it and move on to the next fresh packet.
A hub operates at the physical layer and simply repeats every incoming signal out to all connected ports, with no awareness of which device the data is actually meant for, which means every device on the hub sees every other device's traffic and collisions become common as more devices are added. A switch operates at the data link layer and builds a table mapping MAC addresses to specific ports, so it forwards a frame only to the port where the destination device actually lives, cutting down on both unnecessary traffic and collisions.
A process has its own isolated memory space, file handles, and other OS-level resources. A thread lives inside a process and shares that same memory space and resources with every other thread in that process, holding only its own stack and register state separately. Switching between threads is cheaper because the OS doesn't have to swap out the entire memory mapping and address space the way it does for a full process switch, only the smaller per-thread state needs saving and restoring.
An answer that lands is specific to something real about the company, the actual client work or industry vertical the team you're interviewing for serves, a technology direction they've publicly invested in, or a concrete detail from the job description itself. What doesn't land is anything you could paste into an application to any other company unchanged, "great work-life balance" or "strong learning culture" without a single specific reason attached to Persistent.
Doing five minutes of research on the actual business unit or client account you're interviewing for, rather than the company in general, is the difference between an answer that sounds rehearsed and one that sounds like you actually read the job posting.
The trap in this question isn't ambition, it's specificity that doesn't match the role. Naming a title three levels above what you're interviewing for, or describing a career path in a completely different domain, signals you see this job as a placeholder rather than a real step. A stronger answer connects a believable next two or three years directly to the role in front of you, deeper technical ownership, leading a small piece of a project, moving toward a specific kind of specialization the role naturally leads to, and is honest that the further-out picture will keep evolving as you learn more.
"I work too hard" or "I'm too much of a perfectionist" reads as evasive to any interviewer who's heard it a hundred times, and it signals you're not comfortable with genuine self-assessment. A better answer names something real and specific, a technical area you're actively still building depth in, or a habit you've caught yourself in and are actively correcting, paired with one concrete thing you're doing about it. Specificity, not the underlying content, is what makes the answer credible.
A concrete example beats a general philosophy every time here. Describe an actual situation, what the deadline pressure looked like, how you prioritized what mattered most when not everything could get done, and what the outcome was, rather than a generic statement about staying calm and organized. If you genuinely don't have a strong project example yet, a personal one, an exam crunch, a competing commitment, still works as long as it's specific rather than abstract.
Medium questions
21OOPs and DBMS questions repeat across the largest number of candidate reports, so they're the highest-use review. Networking shows up reliably too but usually at a shallower level, protocol basics rather than subnetting math. Linux questions are the thinnest slice and tend to be command-recognition rather than conceptual, so they're the lowest priority if time is short.
My honest suggestion: spend most of your remaining hours on OOP pillars and normal forms, since those same concepts get re-asked verbally in the technical round anyway. Studying them twice is efficient, not wasted effort.
The Martian Program is Persistent's internal training and internship track that teaches core technologies, commonly reported as Java, MySQL, and Linux, before candidates sit a final evaluation. For candidates who enter through this pipeline rather than a direct campus drive, the assessment content leans more heavily on whatever the program just taught, so recent coursework in that specific stack is worth reviewing right before the test rather than generic CS fundamentals.
Brute force checks every pair with two nested loops, quadratic time, constant space. The hash-based version does a single pass, storing each seen value in a set and checking whether the complement, target minus the current value, already exists before inserting. That trades the quadratic time for linear time at the cost of linear extra space.
public static List<int[]> pairsWithSum(int[] arr, int target) {
Set<Integer> seen = new HashSet<>();
List<int[]> result = new ArrayList<>();
for (int num : arr) {
int complement = target - num;
if (seen.contains(complement)) {
result.add(new int[]{complement, num});
}
seen.add(num);
}
return result;
}Worth mentioning unprompted: duplicate handling. If the array has repeated values, deciding whether to report the same pair twice is a business-logic decision, not a coding one, and calling that out shows you're thinking past the raw algorithm.
Track two running variables, the highest seen so far and the second highest, and update both in a single pass. When the current value beats the highest, the old highest becomes the new second highest before the highest updates. When the current value is strictly between the two, only the second highest updates. This runs in linear time versus the log-linear cost of a full sort.
public static int secondHighest(int[] arr) {
int first = Integer.MIN_VALUE, second = Integer.MIN_VALUE;
for (int n : arr) {
if (n > first) {
second = first;
first = n;
} else if (n > second && n != first) {
second = n;
}
}
return second;
}Recursive backtracking is the standard approach: fix one character at each position by swapping it into place, recurse on the remaining substring, then swap back to restore the original order before trying the next candidate. Time complexity is factorial in the string length, since there are n! distinct arrangements and each one requires linear work to build, giving roughly O(n * n!) overall.
public static void permute(char[] arr, int index) {
if (index == arr.length - 1) {
System.out.println(new String(arr));
return;
}
for (int i = index; i < arr.length; i++) {
swap(arr, index, i);
permute(arr, index + 1);
swap(arr, index, i); // backtrack
}
}A dangling pointer is a pointer that still holds the address of memory that's already been freed or gone out of scope, so dereferencing it reads or writes memory that's no longer valid, or worse, has been reallocated for something else entirely. C and C++ leave memory management to the programmer, so a freed block with a lingering pointer to it is a routine and dangerous bug class.
Java sidesteps this specific failure mode through garbage collection: an object only becomes eligible for collection once nothing reachable references it anymore, so you can't end up holding a live reference to memory the collector has reclaimed. The tradeoff isn't free, Java trades dangling pointers for a different class of bug, memory leaks caused by references that are technically reachable but logically dead, a static collection that never gets cleared, for instance.
Compile-time polymorphism, method overloading, is resolved by the compiler based on the argument types at the call site, so the JVM never has to make a runtime decision about which overload to invoke. Runtime polymorphism, method overriding, is resolved through the object's actual runtime type using a mechanism called dynamic dispatch, the JVM looks up the correct method implementation in a per-class virtual method table at the moment the call actually executes.
class Animal {
void speak() { System.out.println("..."); }
}
class Dog extends Animal {
@Override
void speak() { System.out.println("Woof"); }
}
Animal a = new Dog();
a.speak(); // "Woof", resolved at runtime via the object's actual typeA functional interface is any interface with exactly one abstract method, and it's the type a lambda expression actually compiles down to. Java added the @FunctionalInterface annotation and a set of built-in ones, Runnable, Comparator, Function, Supplier, in Java 8 specifically so lambdas would have a well-defined target type to be assigned to, since Java doesn't support standalone function types the way JavaScript or Python do.
@FunctionalInterface
interface Calculator {
int operate(int a, int b);
}
Calculator add = (a, b) -> a + b;
System.out.println(add.operate(2, 3)); // 5Before Java 8, adding a new method to an interface broke every existing class that implemented it, since Java required a concrete implementation for every interface method. Default methods let an interface ship a method with a body, so existing implementers keep compiling unchanged and only need to override the method if they want different behavior. This is precisely how the Collection interface added Stream-related methods without breaking every collection class already in existence.
interface Greeter {
default void greet() {
System.out.println("Hello");
}
}Normalization organizes a database schema to eliminate redundant data and the update anomalies that redundancy causes. An unnormalized table that repeats a customer's address on every one of their orders means updating that address requires touching every order row, and missing even one leaves the data inconsistent. First normal form removes repeating groups so each cell holds a single value. Second normal form removes partial dependencies on part of a composite key. Third normal form removes transitive dependencies, columns that depend on another non-key column rather than on the key directly.
-- Unnormalized: customer address repeated per order row
-- After 3NF: orders references customers by id, address lives once in customers
CREATE TABLE customers (id INT PRIMARY KEY, name VARCHAR(100), address VARCHAR(200));
CREATE TABLE orders (id INT PRIMARY KEY, customer_id INT REFERENCES customers(id), total DECIMAL);The tradeoff worth mentioning unprompted: normalization reduces redundancy but adds joins, which is exactly why reporting-heavy systems sometimes deliberately denormalize specific tables after the fact for read performance.
Atomicity means a transaction's operations all succeed or all roll back together, nothing partial persists. Consistency means a transaction moves the database from one valid state to another, respecting every constraint and rule. Isolation means concurrent transactions don't see each other's uncommitted intermediate state. Durability means once a transaction commits, it survives a crash immediately after.
A bank transfer is the standard concrete example: debiting one account and crediting another are two separate writes. If the debit succeeds and the system crashes before the credit runs, money simply vanishes unless atomicity guarantees both writes roll back together as a single unit.
An inner join returns only rows that have a match in both tables. A left join returns every row from the left table regardless of whether a match exists in the right table, filling in NULLs for the right side's columns when there's no match. The distinction matters whenever you need to report on the absence of something, all customers including the ones who've never placed an order, for instance, which an inner join would silently drop entirely.
SELECT c.name, o.id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id;
-- customers with zero orders still appear, with o.id as NULLRelational databases enforce a fixed schema and strong consistency guarantees, which is the right default when your data has clear relationships and you need transactional integrity, financial records, inventory counts. NoSQL databases trade some of that structure and consistency for horizontal scalability and schema flexibility, which fits better when the data model changes frequently, the write volume is very high, or the data is naturally document-shaped or graph-shaped rather than tabular.
My honest take: "we might need to scale to millions of users" is not by itself a good reason to pick NoSQL on day one. Most projects that fail on a relational database first fail on bad indexing or a missing cache layer, not on the fundamental limits of SQL itself.
A mutex is a binary lock, exactly one thread can hold it at a time, and typically the same thread that acquired it is the one required to release it, since it's modeling exclusive ownership of a resource. A semaphore holds an integer count and can allow more than one thread through simultaneously up to that count, and any thread can signal it, not just the one that originally waited on it. A mutex answers "is this resource currently in use," a semaphore answers "how many units of this resource are currently available."
IaaS gives you raw virtualized infrastructure, servers, storage, networking, and leaves the operating system, runtime, and application entirely to you. PaaS goes further and manages the operating system and runtime, leaving you responsible only for your application code and data. SaaS hands you a complete, ready-to-use application, and your responsibility shrinks down to configuration and your own data within it.
A useful way to remember the boundary: as you move from IaaS toward SaaS, you trade control for convenience. IaaS gives maximum flexibility and maximum operational burden, SaaS gives minimum flexibility and near-zero operational burden.
A hypervisor is the software layer that creates and manages virtual machines, allocating a physical machine's CPU, memory, and storage across multiple isolated guest operating systems. A Type 1 hypervisor runs directly on the physical hardware with no host OS underneath it, which is what production cloud environments use for lower overhead and better isolation. A Type 2 hypervisor runs as an application on top of an existing host operating system, which is the more common setup for a developer running a VM locally on their laptop.
An Apex trigger is server-side code that fires automatically before or after a specific data operation, insert, update, delete, undelete, on a Salesforce object. Writing a SOQL query inside a loop inside a trigger is one of the most common and most damaging mistakes new Salesforce developers make, because Salesforce enforces strict per-transaction governor limits on the number of queries a single execution context can issue. A trigger firing on a bulk update of two hundred records that queries once per record inside a loop will blow past that limit and fail the entire transaction.
// Wrong: query inside the loop
for (Account acc : Trigger.new) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}
// Right: collect Ids first, query once outside the loop
Set<Id> accountIds = new Set<Id>();
for (Account acc : Trigger.new) accountIds.add(acc.Id);
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];Field injection uses @Autowired directly on a class field, and Spring populates it via reflection after the object is constructed. Constructor injection passes all dependencies as constructor arguments, and Spring populates them at construction time. Constructor injection is the better default because it makes required dependencies explicit and enforced, the object literally can't be constructed without them, and it makes the class trivially testable without needing a Spring context at all, you just call the constructor directly with mocks in a unit test.
@Service
public class OrderService {
private final PaymentClient paymentClient;
public OrderService(PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}
}The honest, low-risk answer is to say so directly and ask whether the timeline can be accelerated, rather than bluffing a deadline that doesn't exist or going silent and letting the process quietly stall. Recruiters generally respect directness here far more than candidates expect, a clear deadline gives them a reason to prioritize your file internally.
What doesn't work is threatening to walk as use in a first-round HR conversation before any offer exists. There's nothing yet to negotiate against, and it reads as premature.
This isn't really a test of the project's outcome, it's a test of whether you can explain a technical decision under mild pressure without hand-waving. A strong answer names the actual problem being solved, why one technical approach was chosen over an alternative you also considered, and at least one thing that didn't work on the first attempt and what you changed. A weak answer describes what the project does at a surface level and stops there the moment a follow-up question probes into any specific technical choice.
The strongest version of this answer centers on how the disagreement actually got resolved, evidence, a small prototype, a shared test case, a compromise that combined both approaches, rather than on who turned out to be right. Interviewers are listening for whether you can disagree on a technical point without it becoming personal or without simply deferring to seniority to avoid the friction.
Hard questions
6A tree is height-balanced if, for every node, the height difference between its left and right subtrees is at most one. The naive approach recomputes height from scratch at every node, giving quadratic time overall. The efficient version computes height and checks balance in the same bottom-up traversal, returning a sentinel value like negative one the moment an imbalance is detected anywhere below, which short-circuits the rest of the recursion.
public static int checkHeight(TreeNode node) {
if (node == null) return 0;
int left = checkHeight(node.left);
if (left == -1) return -1;
int right = checkHeight(node.right);
if (right == -1) return -1;
if (Math.abs(left - right) > 1) return -1;
return Math.max(left, right) + 1;
}The short-circuiting detail is what separates a working answer from an optimal one, plenty of candidates get the balanced-check logic right but still recompute height redundantly, landing back at quadratic time without realizing it.
Modified binary search is the answer. At each step, one half of the array relative to the midpoint is guaranteed to be properly sorted, even though the array as a whole isn't. Check which half is sorted by comparing the endpoints, then check whether the target falls inside that sorted half's range. If it does, search there; if it doesn't, the target must be in the other half.
public static int search(int[] nums, int target) {
int lo = 0, hi = nums.length - 1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (nums[mid] == target) return mid;
if (nums[lo] <= nums[mid]) { // left half sorted
if (nums[lo] <= target && target < nums[mid]) hi = mid - 1;
else lo = mid + 1;
} else { // right half sorted
if (nums[mid] < target && target <= nums[hi]) lo = mid + 1;
else hi = mid - 1;
}
}
return -1;
}A HashMap stores entries in an array of buckets, and a key's hashCode determines which bucket it lands in after being run through the map's internal hash-spreading function. Two keys that produce the same bucket index, a collision, get chained together, historically as a linked list within that bucket, and since Java 8, that chain converts to a red-black tree once it grows past a threshold of eight entries, turning what would be linear-time lookups within a badly collided bucket into logarithmic time instead.
Map<String, Integer> counts = new HashMap<>();
counts.put("a", 1);
counts.put("b", 2);
// lookup: hash("a") determines bucket, equals() confirms exact key match
// within that bucket if multiple keys hashed to the same slotA detail that trips people up: a good hashCode implementation reduces collisions but doesn't eliminate the need for equals, HashMap always calls equals to confirm an exact key match once it's found the right bucket, since different keys can legitimately share a hash code.
An index is a separate, ordered data structure, typically a B-tree, that lets the database locate rows matching a condition without scanning the entire table. It turns a linear scan into a logarithmic lookup for queries that filter or sort on the indexed column.
The cost is on the write side: every insert, update, or delete has to update every index on that table, not just the underlying data, so a table with ten indexes pays that overhead ten times over on every write. Indexes also consume disk space and memory in the buffer cache. The right number of indexes is the smallest set that covers your actual query patterns, not the maximum you can add.
A channel is Go's idiomatic way to pass ownership of data between goroutines, communicating by sharing memory through the channel rather than sharing memory directly and protecting it with a lock. A mutex is the more traditional approach, multiple goroutines share the same memory and coordinate access to it with an explicit lock. Go's own guidance leans toward channels for passing data and signaling completion, and toward a mutex for protecting a small, frequently accessed shared piece of state, a counter or a cache, where the overhead of a channel round-trip isn't worth it.
var mu sync.Mutex
counter := 0
func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}In practice, real Go services use both: channels to coordinate work distribution and completion signaling across goroutines, and a mutex or atomic operation to protect a specific hot piece of shared state that would be awkward to route through a channel on every access.
Hibernate's first-level cache is scoped to a single Session and stores every entity that's been loaded or saved during that session, so a second request for the same entity by its identifier within that same session returns the cached instance rather than issuing another database query. It's on by default and can't be disabled.
The surprise shows up in long-running transactions that load a large number of distinct entities: every one of them stays attached to the session and consumes memory for the session's entire lifetime, and if you're not careful about detaching entities you no longer need, a batch job processing a huge dataset inside one long session can quietly balloon memory usage well past what the actual working set requires at any one moment.
How to prepare for a Persistent Systems interview
Given how often the online assessment and first technical round lean on OOP fundamentals and SQL over raw algorithmic difficulty, the highest-use prep is writing out, from memory and without notes, clean explanations of encapsulation versus abstraction, normalization through third normal form, and the ACID properties, then checking your explanation against a textbook definition rather than assuming you already know it cold. Candidates consistently describe getting tripped up not by hard problems but by fundamentals they thought were too basic to review.
If you're interviewing for a specific stack, Java and Spring Boot, Go, Salesforce, treat the domain-specific section above as the actual syllabus rather than the DSA questions, since multiple reports describe the second technical round scoping almost entirely to the team's real technology rather than generic problem-solving. Read the actual job posting for keywords, Docker, Kubernetes, Hibernate, Apex, and prepare one concrete example of using each one you claim familiarity with.
Get the reps in before the real thing
LastRoundAI's mock interview mode runs live technical and behavioral rounds with real-time follow-up questions, which matters here specifically because Persistent's HR round rewards specificity under a follow-up far more than a polished opening answer. The free plan includes 15 credits a month that reset monthly, and Starter is $19/mo if a handful of sessions isn't enough runway before your actual interview date.
Once your answers hold up to a follow-up question, Auto-Apply queues tailored applications to roles that match your background for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.
Questions about either product go to contact@lastroundai.com. That's the only inbox we check.
How this list was built
Worth being straight about where these questions come from, because plenty of pages in this category are not. The set was compiled from a research pass across official documentation, vendor release notes, published engineering writing and public discussion of hiring processes, then cross-checked against the current version of each technology so nothing here describes behaviour that has since changed.
What that means in practice: these are the questions the material supports as reasonable and current for this role, not a transcript of any one company's loop. We have not sat in on your interview and we are not going to claim we have. Treat the list as well-sourced preparation rather than a leaked question bank, and expect your panel to phrase things their own way.
If you spot something out of date, tell us at contact@lastroundai.com and we will fix it.
Frequently asked questions
What happens in the Persistent Systems HR round?
Relocation flexibility, shift willingness, the service agreement or bond if one applies, and why you want this company specifically. It is largely a fit and commitment check, but people do get rejected here, usually for appearing uninterested or for contradicting something on their form.
How long does the Persistent Systems hiring process take?
Commonly two to six weeks from first assessment to offer, though campus drives can compress it to a few days and lateral roles sometimes stretch longer. Silence in between is normal and is not usually a signal either way.
How many rounds does the Persistent Systems interview have?
Most Persistent Systems processes run an online aptitude and technical assessment, then one or two technical interviews, then an HR round. Lateral hiring often drops the aptitude test and goes straight to technical. The exact shape varies by campus drive versus off-campus, so confirm with your recruiter rather than assuming.
Is the Persistent Systems aptitude test hard to clear?
It is more about pace than difficulty. The quantitative and logical sections are standard campus-placement material, but the time per question is tight, so accuracy under time pressure is what actually filters people out. Practising with a clock matters more than learning new topics.
