Coforge Interview Questions · 2026

Coforge Interview Questions (2026): Real, With Answers

A candidate applying for a Software Engineer role at Coforge in late 2025 described the whole loop as oddly calm compared to what he'd braced for after two rounds at a product company that grilled him on segment trees. Coforge's process, by most accounts, front-loads an online assessment (aptitude, technical MCQs, and a short coding section) and then leans on a technical round that stays close to what you'd actually touch on the job: Core Java, SQL, a live project walkthrough, some scenario questions. Software Engineer candidates rate the process 2.7 out of 5 for difficulty and 61 percent describe the experience as positive, with a median time-to-hire around five days across submitted reports (Glassdoor, Coforge Software Engineer interviews).

That doesn't mean it's easy to sleepwalk through. Coforge runs both campus and lateral hiring at volume, for developer, QA/automation, and senior engineering roles, and the questions candidates report skew fundamentals-heavy rather than obscure. Expect Core Java and Hibernate distinctions that separate people who've actually shipped Spring Boot services from people who memorized a cheat sheet, SQL queries you'd write on a whiteboard without an IDE's autocomplete, and, for QA-track candidates, real API and Selenium scenarios rather than trivia. One recurring theme in Java-developer write-ups: interviewers push past "what is X" into "why would you pick X over Y in this specific case," which is where a lot of otherwise-solid candidates start improvising badly (GeeksforGeeks, Coforge Software Engineer interview experience).

This page groups roughly 50 questions the way Coforge's own rounds are actually structured: the online assessment, Core Java and OOP, SQL, Spring/microservices, data structures, a lighter system-design slice for senior candidates, the QA/automation track, and HR. If a round is thin on public detail, we say so instead of inventing specifics.

~50Questions
4Rounds
90 minOA Time
2.7/5SE Difficulty

Online assessment: aptitude, MCQs, and a short coding round

Most candidates hit this before anyone from Coforge talks to them. Reports converge on roughly 90 minutes split across three timed sections: aptitude (around 30 minutes), technical MCQs (around 35), and a coding section (around 25). None of it is exotic, but the clock is real and a lot of candidates lose time on the MCQ section second-guessing OOP definitions they actually know.

Easy questions

14

Three timed sections in one sitting: quantitative aptitude and logical reasoning first, then technical MCQs pulled from programming fundamentals, OOP, operating systems, computer networks, and SQL, then a short coding section with one or two problems. The whole thing runs about 90 minutes, and candidates who've gone through it describe the coding problems as closer to "can you write correct, working code quickly" than anything algorithmically deep.

The practical implication: don't spend your prep time on hard graph problems for this stage. Spend it making sure you can write a clean loop, a string manipulation function, or a basic recursive solution without syntax errors under a countdown timer.

Two common approaches: arithmetic (addition/subtraction, or XOR) both avoid a temp variable by encoding both values' information into a single slot temporarily.

java
int a = 5, b = 10;

a = a + b; // a = 15
b = a - b; // b = 5
a = a - b; // a = 10

// XOR version, avoids overflow risk with large ints
a = a ^ b;
b = a ^ b;
a = a ^ b;

Interviewers usually want you to say out loud why the XOR version is preferred for integer types close to overflow boundaries, since a + b can overflow before the subtraction step corrects it.

Logical reasoning tests pattern recognition, syllogisms, seating arrangements, series completion, the kind of question that has one correct mechanical approach once you've seen the pattern before. Verbal ability leans on reading comprehension, sentence correction, and vocabulary in context.

Neither section by itself disqualifies a strong technical candidate on its own, but a candidate who blows through half the allotted time misreading a seating-arrangement puzzle has that much less time for the coding section, and the sections are usually not independently timed in a way that lets you borrow minutes back. Treat the full 90 minutes as one budget, not three separate exams.

A process is an independent unit of execution with its own memory space. A thread is a lighter-weight unit of execution within a process, sharing that process's memory and resources with other threads in the same process. That's why threads communicate faster than processes but also why a bug in one thread can corrupt shared state for every other thread in the same process.

Coforge asks this at the MCQ stage because a huge share of the actual engineering work is on service-oriented Java applications, where thread pools, connection pools, and shared mutable state cause real production incidents. It's fundamentals, but fundamentals that map directly onto bugs you'll actually debug.

get() hits the database immediately and returns null if the row doesn't exist. load() returns a lazy proxy without touching the database until you actually access a field on the returned object, and it throws an ObjectNotFoundException at that point if the row is missing rather than returning null.

java
User u1 = session.get(User.class, 1); // query runs now; null if not found
User u2 = session.load(User.class, 1); // proxy only; query runs on first field access
System.out.println(u2.getName()); // exception here if row 1 doesn't exist

Use load() when you're confident the row exists and want to defer the actual query, common for setting up a foreign-key reference. Use get() anywhere you need to handle a missing row as a normal case rather than an exception.

Once created, a String's internal character data can't be changed. Every operation that looks like it modifies a string, concatenation, replace, substring, actually returns a new String object and leaves the original untouched. That immutability is why Strings are safe to share across threads without synchronization and why the string pool (interned literals) can safely reuse the same object for identical literal values.

The performance cost shows up when you build a string incrementally in a loop with +, each concatenation allocates a new object, so a loop of a thousand concatenations creates roughly a thousand throwaway String objects for the garbage collector to clean up. That's the entire reason StringBuilder exists, it mutates an internal buffer in place instead of allocating a new object per operation.

The classic version uses a subquery to exclude the max before taking the max of what's left. It's worth knowing at least two ways to write this, because interviewers sometimes ask you to avoid a specific clause.

sql
SELECT MAX(salary) AS second_highest
FROM Employee
WHERE salary < (SELECT MAX(salary) FROM Employee);

-- alternative using DENSE_RANK, handles ties differently than the subquery version
SELECT salary FROM (
 SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
 FROM Employee
) ranked
WHERE rnk = 2;

The DENSE_RANK version is worth mentioning even if not asked directly, it handles duplicate top salaries correctly (two people tied for first still gives you a real second-place value), while the subquery version silently returns the same tied value if two people share the top salary.

Group by the columns that define a duplicate, then filter groups with more than one row using HAVING, since WHERE can't filter on an aggregate.

sql
SELECT email, COUNT(*) AS occurrences
FROM Users
GROUP BY email
HAVING COUNT(*) > 1;

If the interviewer wants the actual duplicate rows themselves, not just the count, that's a self-join or a window function (ROW_NUMBER partitioned by the duplicate-defining columns), a natural follow-up worth anticipating.

byName wires a dependency by matching the bean's property name to a bean id in the container. byType wires by matching the property's type to exactly one bean of that type, and it fails if more than one bean of that type exists without a qualifier to disambiguate. Constructor autowiring injects dependencies through the constructor rather than a setter, which is the mode most modern Spring code defaults to because it makes required dependencies explicit and lets you mark fields final.

java
@Service
public class OrderService {
  private final PaymentClient paymentClient; // constructor injection, no @Autowired needed since Spring 4.3 with a single constructor

  public OrderService(PaymentClient paymentClient) {
    this.paymentClient = paymentClient;
  }
}

Two-pointer swap from both ends toward the middle, standard and fast to write correctly under time pressure.

java
char[] arr = "coforge".toCharArray();
int left = 0, right = arr.length - 1;
while (left < right) {
  char temp = arr[left];
  arr[left] = arr[right];
  arr[right] = temp;
  left++;
  right--;
}
System.out.println(new String(arr)); // egrofoc

Sum formula beats sorting or a hash set here, O(n) time, O(1) space, and it's the answer an interviewer expects you to reach for immediately rather than after being nudged.

java
int n = arr.length + 1; // one number is missing from 1..n
int expectedSum = n * (n + 1) / 2;
int actualSum = 0;
for (int num : arr) actualSum += num;
int missing = expectedSum - actualSum;

Smoke testing is a quick, shallow pass over the most critical functionality, does the app even start, can a user log in, right after a new build, meant to catch a fundamentally broken build before investing time in deeper testing. Regression testing is a much broader re-run of existing test cases after a code change, to confirm the change didn't break something that was previously working elsewhere in the application.

The practical distinction that matters on the job: smoke tests need to run fast, often as a gate before a build is even accepted for further testing, while regression suites can be large and slow, and teams frequently automate them specifically because running the full suite manually after every change isn't sustainable.

Start with the obvious positive case, valid credentials succeed, then work through the negative space methodically: wrong password, non-existent username, empty fields, SQL injection attempts in the input fields, password field masking, account lockout after repeated failed attempts, session behavior after successful login, and case sensitivity of the username or email field.

A strong answer also covers non-functional angles the interviewer is listening for: does the page behave correctly under a slow network, what happens if the user double-clicks submit, is the password ever visible in a network request log or browser autofill in a way it shouldn't be. Candidates who only list functional cases usually get a follow-up nudging toward these.

Skip the résumé recap, the interviewer already has it in front of them. A stronger version connects a specific piece of your recent work to something concrete about the role you're interviewing for, and gives an honest, specific reason for the move rather than a generic line about "growth opportunities" that could apply to any company.

If you're coming from a similar services or consulting background, it's fine, and often more credible, to say plainly that you're looking for a particular kind of project exposure, a specific domain, a specific tech stack, rather than inventing enthusiasm you don't actually feel about the company as an abstraction.

Medium questions

25

This is a direct test of whether you reach for Streams naturally or fall back to a manual HashMap loop, both work, but the interviewer is checking which one comes to mind first.

java
String input = "coforge";

Map<Character, Long> freq = input.chars()
 .mapToObj(c -> (char) c)
 .collect(Collectors.groupingBy(c -> c, Collectors.counting()));

System.out.println(freq); // {c=1, o=2, f=1, r=1, g=1, e=1}

Worth mentioning unprompted: groupingBy with a downstream collector like counting() is the pattern that shows up again and again in the SQL-style questions later in this same interview, grouping employees by department, finding averages per category. It's the same mental model applied to a Java collection instead of a table.

Trace it line by line on scratch paper rather than eyeballing it, MCQ writers deliberately pick operator combinations, like mixing ++, implicit narrowing casts, and short-circuit evaluation, specifically because they're easy to get wrong at a glance.

java
int x = 5;
int y = x++ + ++x; // x++ evaluates to 5, then x becomes 6; ++x makes x 7, evaluates to 7
System.out.println(x + " " + y); // 7 12

If you can't fully trace it in the time you have, eliminate answer choices that violate a rule you're sure of (like final variable type) rather than guessing cold. Partial certainty beats a blind guess on a multiple-choice section.

save() inserts a new row and returns the generated identifier immediately, even outside a transaction boundary in some configurations. persist() is the JPA-standard equivalent, it doesn't guarantee an immediate identifier return and is meant to be used strictly inside a transaction. saveOrUpdate() inspects the entity's identifier and existing state to decide whether to run an insert or an update, useful when you genuinely don't know if the entity already exists in the database.

In practice, most modern Spring Data JPA code sidesteps this whole distinction by calling repository.save(), which internally decides between persist and merge based on whether the entity has an ID set. Knowing the underlying Hibernate methods still matters because that's what Spring Data is calling at the implementation level, and it's exactly the kind of thing an interviewer asks to see if you understand the abstraction or just trust it blindly.

HashMap isn't thread-safe. Two threads writing to it at the same time can corrupt its internal bucket structure, in older Java versions this could even cause an infinite loop during a resize. ConcurrentHashMap achieves thread safety without locking the entire map for every operation, it segments internal locking so that writes to different buckets can happen concurrently, and reads generally don't block at all.

java
Map<String, Integer> unsafe = new HashMap<>(); // never share across threads without external sync
Map<String, Integer> safe = new ConcurrentHashMap<>(); // safe for concurrent read/write

A follow-up worth having an answer ready for: ConcurrentHashMap doesn't make compound operations like "check if absent, then put" atomic by default, you still need putIfAbsent() or compute() for that, a plain containsKey() followed by put() is still a race condition even on a concurrent map.

The main ones you're expected to know by name: Serial GC (single-threaded, fine for small heaps and single-core environments), Parallel GC (multi-threaded, throughput-focused, the old default), CMS (Concurrent Mark Sweep, deprecated in favor of newer options), G1 (Garbage First, the default since Java 9, balances throughput and pause time by collecting in regions), and ZGC and Shenandoah (both aim for sub-millisecond pause times even on multi-terabyte heaps).

The honest answer most teams give: G1 is the right default for the vast majority of services, and you only reach for ZGC or Shenandoah once pause-time requirements get aggressive enough that G1's pauses, still typically tens of milliseconds under load, actually show up in your latency budget.

No, at least not once the set has more than zero elements. TreeSet keeps its elements sorted, which means it has to compare every new element against existing ones using either natural ordering (compareTo) or a supplied Comparator. Comparing null against anything throws a NullPointerException, so adding a single null to an empty TreeSet technically works, but adding a second element, or adding null to a non-empty set, throws.

java
TreeSet<String> set = new TreeSet<>();
set.add(null); // works, set is empty
set.add("a"); // NullPointerException, comparing "a" against null

This is a good one to contrast against HashSet, which allows exactly one null element with no restriction, because HashSet relies on hashing and equals(), not ordering comparisons.

Sealed classes let you restrict which classes can extend or implement a type, giving you exhaustiveness guarantees closer to what pattern matching languages offer. Records give you an immutable data class with equals, hashCode, and toString generated for you in one line instead of the usual boilerplate. Pattern matching for instanceof removes the redundant cast you used to write after every instanceof check. Text blocks make multi-line strings, SQL queries, JSON payloads, readable without a wall of escaped quotes and plus signs.

The practical reason this comes up in interviews: Java 8 is well past its official support runway, and a lot of Coforge's client engagements are migrations off legacy stacks, so knowing what changed matters more here than it might at a company still comfortably on an older LTS.

A functional interface declares exactly one abstract method, which is what makes it a valid target for a lambda expression or method reference. Runnable, Comparator, and Function are all functional interfaces. Default methods let an interface provide a method body directly on the interface itself, so implementing classes get the behavior for free unless they choose to override it.

java
interface Greeter {
  void greet(String name); // the single abstract method

  default void greetLoudly(String name) {
    greet(name.toUpperCase());
  }
}

Default methods exist specifically because of the Streams API rollout. Adding a new abstract method to an existing interface like List would break every class that already implements it. A default method lets the JDK add new capability, forEach(), stream(), to interfaces that millions of existing classes already implement, without forcing every one of them to add a new method or fail to compile.

NOT EXISTS or a LEFT JOIN with a NULL check both work, and interviewers sometimes ask you to write it both ways to see if you understand why they're equivalent.

sql
-- NOT EXISTS version
SELECT a.id FROM TableA a
WHERE NOT EXISTS (
 SELECT 1 FROM TableB b WHERE b.id = a.id
);

-- LEFT JOIN version
SELECT a.id FROM TableA a
LEFT JOIN TableB b ON a.id = b.id
WHERE b.id IS NULL;

NOT EXISTS generally performs better on large tables because the query planner can short-circuit as soon as it finds one match, versus a LEFT JOIN which has to materialize the full join before filtering. Worth saying out loud if the interviewer asks which one you'd actually use in production.

INTERSECT does this directly if the database supports it, otherwise an INNER JOIN or an EXISTS subquery gets the same result.

sql
SELECT id FROM TableA
INTERSECT
SELECT id FROM TableB;

-- equivalent using EXISTS
SELECT a.id FROM TableA a
WHERE EXISTS (SELECT 1 FROM TableB b WHERE b.id = a.id);

This is a direct test of whether you know SQL's wildcard syntax cold, not just SELECT and WHERE. Underscore matches exactly one character, percent matches zero or more.

sql
SELECT name FROM Employee
WHERE name LIKE '__hn%';
-- __ matches any two leading characters, hn is literal, % matches the rest

A name like "John" fits this pattern: J and o are the two wildcard characters, hn matches literally, and n is what's left after that (with nothing following, since % also matches zero characters). It's a small detail, but interviewers use this exact pattern because it exposes candidates who only know % and never learned the single-character wildcard.

This is really three separate SQL skills stacked into one problem: a WHERE filter, a string transformation, and a GROUP BY aggregate. Worth breaking down out loud so the interviewer sees you're not just pattern-matching a memorized query.

sql
SELECT
 CONCAT(UPPER(LEFT(customer_name, 1)), LOWER(SUBSTRING(customer_name, 2))) AS customer_name_formatted,
 category,
 AVG(amount) AS avg_transaction
FROM Transactions
WHERE amount > 10000
GROUP BY category, customer_name;

Watch the GROUP BY carefully here, if the average needs to be per category regardless of customer, customer_name shouldn't be in the GROUP BY at all. Interviewers use ambiguous phrasing like this on purpose to see if you ask a clarifying question instead of guessing at the grouping.

Primary key uniquely identifies a row and can't be null. Foreign key references a primary key in another table, enforcing referential integrity, you can't insert a row pointing at a parent that doesn't exist. Unique key enforces uniqueness like a primary key but allows one null value and a table can have several. Composite key is a primary key made of more than one column together, common when no single column is unique on its own. Candidate key is any column or column set that could have served as the primary key; the one you pick becomes primary, the rest remain candidate keys.

The reason a table needs more than a primary key: the primary key protects one table's own integrity, but real schemas have relationships between tables, and foreign keys are what stop an order from referencing a customer_id that was deleted three weeks ago.

Spring's singleton scope means one instance of that bean exists per Spring container (application context), not one instance per JVM the way the classic Singleton pattern enforces. If you run two separate Spring contexts in the same JVM, each gets its own "singleton" instance of the bean, which is a meaningfully different guarantee than the GoF pattern's global single instance.

The practical consequence: singleton-scoped beans in Spring should be stateless, or carefully manage any shared mutable state, because the same instance handles every request across every thread that touches it. A singleton bean with a mutable instance field that isn't thread-safe is a bug waiting to surface under concurrent load, and it's a common thing interviewers probe for by asking what happens if you add a mutable field to a singleton service.

It's a convenience annotation that bundles three separate annotations: @Configuration (marks the class as a source of bean definitions), @EnableAutoConfiguration (tells Spring Boot to guess and configure beans based on what's on the classpath, a DataSource bean if it sees a JDBC driver, for instance), and @ComponentScan (tells Spring to scan the current package and sub-packages for other annotated components).

java
@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

A candidate who can name what auto-configuration actually inspects, dependencies on the classpath plus any properties you've set, is answering from real experience. A candidate who just says "it starts the app" is repeating the tutorial without understanding what's happening.

@RequestBody deserializes the entire HTTP request body, typically JSON, into a Java object, used for POST and PUT endpoints that receive a structured payload. @RequestParam pulls a single value from the query string or form data, used for simple filters, pagination parameters, that kind of thing.

java
@PostMapping("/users")
public User createUser(@RequestBody UserRequest request) {... }

@GetMapping("/users")
public List<User> getUsers(@RequestParam(defaultValue = "0") int page) {... }

Mixing them up in a real endpoint design, trying to pass a full object through query parameters, is the kind of mistake this question is really testing for.

All three are specializations of @Component and get picked up identically by component scanning, so functionally they're interchangeable at the container level for basic bean registration. The difference that isn't just cosmetic: @Repository adds automatic exception translation, converting persistence-specific exceptions (a JDBC SQLException, for instance) into Spring's unchecked DataAccessException hierarchy, so your service layer doesn't have to catch vendor-specific exception types.

Using @Service and @Repository correctly by layer, rather than sprinkling @Component everywhere, isn't required for the app to work, but it's a real signal in a code review that you understand the layered architecture you're building, and interviewers use this question specifically to catch candidates who've only ever copy-pasted whichever annotation showed up in a tutorial.

Brute force is O(n squared), checking every pair. A hash map gets it to a single O(n) pass by storing each value's complement as you go, and checking whether the current number already has its match seen earlier in the array.

java
Map<Integer, Integer> seen = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
  int complement = target - nums[i];
  if (seen.containsKey(complement)) {
    return new int[]{seen.get(complement), i};
  }
  seen.put(nums[i], i);
}

The follow-up worth being ready for: what if the array has duplicate values, or what if you need all pairs instead of just one. Both change the approach slightly, and interviewers ask them specifically to see if the O(n) solution was memorized or actually understood.

Sorting both strings and comparing is the simplest correct answer, O(n log n). A frequency-count comparison gets you to O(n) and is the answer that shows a bit more thought.

java
public boolean isAnagram(String a, String b) {
  if (a.length() != b.length()) return false;
  int[] counts = new int[26];
  for (char c : a.toCharArray()) counts[c - 'a']++;
  for (char c : b.toCharArray()) counts[c - 'a']--;
  for (int count : counts) if (count != 0) return false;
  return true;
}

Binary search twice, once biased to find the leftmost match, once biased to find the rightmost, instead of a single linear scan.

java
int findBound(int[] arr, int target, boolean findFirst) {
  int lo = 0, hi = arr.length - 1, result = -1;
  while (lo <= hi) {
    int mid = lo + (hi - lo) / 2;
    if (arr[mid] == target) {
      result = mid;
      if (findFirst) hi = mid - 1; else lo = mid + 1;
    } else if (arr[mid] < target) {
      lo = mid + 1;
    } else {
      hi = mid - 1;
    }
  }
  return result;
}

The reason this shows up as a "sorted array" question rather than a generic search problem: it's a direct test of whether a candidate reaches for binary search whenever the input is sorted, or defaults to a linear scan out of habit even when a faster option is obviously available.

Avoid brittle locators tied to auto-generated IDs or absolute XPath, and instead anchor to something stable: a data-testid attribute if the application exposes one, a relative XPath built off stable text or a parent element's stable attribute, or a CSS selector using a class that's part of the application's actual styling contract rather than a generated hash.

java
// brittle: id changes on every render
driver.findElement(By.id("btn-4821"));

// stable: anchors to visible text and structure instead of a generated id
driver.findElement(By.xpath("//button[contains(text(),'Submit')]"));

For elements that load asynchronously, explicit waits (WebDriverWait with an ExpectedCondition) beat a hardcoded Thread.sleep every time, sleep either wastes time waiting longer than necessary or fails intermittently when the element takes slightly longer to appear than the fixed sleep duration.

Status code is the first check, but it's the shallowest one. Beyond that: response schema (are all expected fields present and correctly typed), response time against an agreed SLA, headers (content-type, caching directives, correct auth challenge on an unauthorized request), error response structure on failure cases (a 400 should return a body that actually explains what was wrong, not just the code), and idempotency for methods that are supposed to be idempotent, calling the same PUT or DELETE twice should produce the same end state, not a different error the second time.

java
given()
 .when().get("/api/users/42")
 .then()
 .statusCode(200)
 .body("id", equalTo(42))
 .body("email", notNullValue())
 .time(lessThan(500L));

A candidate who only checks status code and stops there is testing that the server responded, not that it responded correctly, and that gap is exactly what this question is probing for.

Severity measures how badly the bug impacts the application's functionality, technical impact. Priority measures how urgently it needs to be fixed relative to other work, business impact. They usually move together, but not always.

A clear divergence: a typo in a rarely-visited legal disclaimer page is low severity (nothing breaks) but might be high priority if a compliance deadline forces an immediate fix. Conversely, a crash that only occurs in an obscure edge case affecting a feature almost nobody uses can be high severity (the app genuinely crashes) but low priority (few users ever hit that path, so it can wait for the next release cycle). Interviewers ask for the divergent example specifically because it's the part candidates who've only read a definition off a glossary usually can't produce on the spot.

Interviewers are listening for specifics that only someone who actually sat through the process would know: how sprints were scoped, what a typical standup covered, how a story got from backlog to done, what happened when a sprint commitment slipped. A candidate who only says "we used Agile" without any of that texture reads as someone repeating a buzzword from a job description rather than someone who lived inside the process.

It's fine, and often stronger, to mention a moment the process didn't go smoothly, a story that got re-estimated mid-sprint, a retro that led to an actual process change, rather than describing a version of Agile where everything always went according to plan.

The answer that lands is specific about the actual technical disagreement, not just the interpersonal resolution. Name the real trade-off (a library choice, a schema design, a testing approach) and explain what evidence or reasoning actually moved the decision, a benchmark, a production incident, a senior engineer's tie-breaking call, rather than a vague "we talked it out and compromised."

It's also fine to describe a disagreement you lost, and what you learned from being wrong, that's often a more convincing answer than a story where you were right all along and everyone eventually agreed with you.

Hard questions

5

synchronized enforces mutual exclusion, only one thread executes the guarded block at a time, and it also establishes a happens-before relationship so changes made inside the block are visible to the next thread that enters it. volatile guarantees visibility of a single variable's latest value across threads without enforcing mutual exclusion, it stops a thread from reading a stale cached copy, but it doesn't make compound operations like increment atomic. Atomic classes (AtomicInteger, AtomicLong, and friends) give you lock-free, compare-and-swap-based atomicity for specific operations like increment-and-get, without the overhead of acquiring a full lock.

java
volatile boolean running = true; // visibility only, fine for a simple flag

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // atomic read-modify-write, no lock needed

synchronized (lock) {
  // use when you need to guard multiple related fields together, not just one
}

The mistake candidates make most often: reaching for volatile on a counter that gets incremented from multiple threads. volatile fixes visibility, not the read-modify-write race in counter++. That specific bug is exactly what Atomic classes exist to close.

The heap holds every object you create with new, and it's shared across all threads, which is why it needs garbage collection at all. The stack is per-thread, holding local variables and method call frames, and it's reclaimed automatically the instant a method returns, no GC involved, which is also why a deep enough recursive call blows the stack with a StackOverflowError rather than an OutOfMemoryError. Metaspace (replacing PermGen since Java 8) holds class metadata, method bytecode, and the constant pool, and it grows dynamically from native memory rather than being capped by a fixed heap-style size the way PermGen was.

The follow-up that separates a memorized answer from a working one: what causes a memory leak in a language with garbage collection? Usually a reference that's still reachable but logically dead, a static collection that keeps growing, listeners that never get deregistered, a cache with no eviction policy. GC can't collect what's still reachable, even if your code will never use it again.

An API Gateway sits in front of every downstream microservice and gives external clients a single entry point, handling routing, authentication, rate limiting, and sometimes response aggregation from multiple services in one round trip. Skip it, and every client has to know the network location of every individual service, cross-cutting concerns like auth get duplicated across each service instead of centralized once, and any change to your service topology, splitting one service into two, becomes a client-facing breaking change instead of an internal refactor.

The trade-off worth mentioning: a gateway is also a new single point of failure and a new latency hop, so it needs its own redundancy and shouldn't become a dumping ground for business logic that belongs in the services themselves.

Floyd's tortoise and hare: two pointers moving at different speeds through the list. If there's a cycle, the fast pointer eventually laps the slow one and they meet inside the loop. If there's no cycle, the fast pointer reaches null first.

java
public boolean hasCycle(ListNode head) {
  ListNode slow = head, fast = head;
  while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow == fast) return true;
  }
  return false;
}

A good follow-up to have ready without being asked: once you've detected a cycle, finding exactly where it starts requires resetting one pointer to the head and advancing both one step at a time until they meet again, that meeting point is the cycle's entry node. It's a small additional insight that shows you understand the algorithm's proof, not just its implementation.

Eventual consistency buys you availability and lower latency, a write can succeed and return immediately without waiting for every replica or downstream system to confirm it's seen the update, which matters a lot under high write volume or across geographically distributed systems where waiting for full consistency would mean waiting on cross-region network latency for every write.

The trade you're accepting: a reader might see stale data for some window after a write, seconds, sometimes longer under replication lag. That's fine for a product catalog view count or a "likes" counter. It's not fine for an account balance, an inventory count that gates whether an order can be placed, or anything where two users acting on stale data can cause a real-world conflict, double-booking the same seat, overselling the last unit of stock. Those cases need strong consistency even at the cost of latency, and knowing which category a given feature falls into is the actual skill being tested here, not the definition of eventual consistency itself.

Real-time scenario questions

6

Generate a unique correlation ID at the edge, the API Gateway or the very first service that receives an external request, and propagate that ID through every downstream call, in HTTP headers for synchronous calls, in message metadata for anything async through a queue. Every service logs that same correlation ID alongside its own logs, so a single request's full path across a dozen services can be reconstructed by searching logs for one ID instead of manually correlating timestamps across a dozen separate log streams.

In practice this is usually built on something like OpenTelemetry or Spring Cloud Sleuth generating the IDs automatically and a system like Jaeger or Zipkin visualizing the resulting trace as a timeline, rather than teams hand-rolling correlation ID propagation themselves.

Three core tables, normalized to avoid duplicated data: Customers (customer_id, name, email), Products (product_id, name, price), and Orders (order_id, customer_id as a foreign key), with a fourth join table, OrderItems, holding order_id, product_id, quantity, and the price at time of purchase, since product price can change after an order is placed and the historical order needs to preserve what was actually charged.

That last detail, storing price_at_purchase on OrderItems rather than joining live against the current Products table, is the kind of thing that separates a candidate who's actually built an e-commerce-adjacent system from one who's only drawn the schema on a whiteboard. Interviewers who've built real systems ask about it specifically.

A composite index on both columns, in the right order, usually beats two separate single-column indexes for a query that filters on both simultaneously. Column order in the composite index matters: put the column with higher selectivity (more distinct values, meaning it narrows the result set more) first, since the index is only useful as a range scan on a prefix of its columns.

sql
CREATE INDEX idx_status_created ON Orders (status, created_at);
-- fast for: WHERE status = 'shipped' AND created_at > '2026-01-01'
-- not usable for: WHERE created_at > '2026-01-01' alone, since status isn't in the filter

An index that isn't used by the query planner, because column order doesn't match how the query filters, doesn't just fail to help, it adds write overhead on every insert and update for no read benefit. That trade-off is worth stating plainly if the interviewer asks whether you'd just index everything.

The pattern most teams reach for is a circuit breaker: after a configured failure threshold, the circuit "opens" and stops sending requests to the failing service for a cooldown period, failing fast with a fallback response instead of piling up threads waiting on a service that's already struggling. Combine that with timeouts on every network call (never rely on a default that might be measured in minutes), bulkheads that isolate thread pools per downstream dependency so one slow service can't exhaust threads needed by unrelated calls, and retries with backoff for genuinely transient failures only.

java
@CircuitBreaker(name = "inventoryService", fallbackMethod = "fallbackInventory")
public InventoryResponse checkInventory(String sku) {
  return inventoryClient.check(sku);
}

public InventoryResponse fallbackInventory(String sku, Throwable t) {
  return InventoryResponse.unknown(sku); // degrade gracefully instead of failing the whole request
}

The failure mode this whole pattern exists to prevent is one slow or dead service taking down every service that calls it, and then every service that calls those, until the whole system is unresponsive from a single point of failure three hops away.

Cache first, before anything more invasive: put a read-through cache (Redis, or an in-process cache for data that's small and changes rarely) in front of the expensive query path, since a huge share of read spikes are the same handful of queries repeated thousands of times rather than truly unique requests. If caching alone doesn't cover it, add read replicas for the database and route read traffic to them, keeping writes on the primary. Horizontal scaling of the service itself, more stateless instances behind a load balancer, is usually the next lever, and it only works cleanly if the service is actually stateless, no in-memory session data tied to a specific instance.

The order matters here. Jumping straight to "add more instances" without addressing what's actually expensive, usually a database query or an external call, just moves the bottleneck to the database instead of fixing it, and a database that's already struggling under load doesn't get healthier because ten more service instances are all hitting it in parallel.

This question is really testing composure and method under pressure, not whether you fixed the bug in record time. A strong answer walks through the actual diagnostic sequence, what you checked first and why, what red herring you initially chased before finding the real cause, how you communicated status while the issue was still open, rather than jumping straight to the resolution as if the cause was obvious from the start.

Being honest about a mistake made during the incident, a wrong initial hypothesis, a rollback that didn't fully work the first time, usually reads as more credible than a version where every step was correct in hindsight. Interviewers who've actually been on-call themselves can tell the difference.

How to prepare for a Coforge interview

Don't over-index on algorithm difficulty here. Candidate reports consistently describe the bar as fundamentals executed correctly under time pressure, not competitive-programming-level problems. The highest-use prep is making sure you can write a clean SQL query without an IDE's help, explain the Hibernate and Spring annotations you actually use rather than just recognize, and talk through your own project in enough concrete detail that it's obvious you built it rather than described it secondhand.

If you're on the QA or automation track specifically, the gap between candidates who pass and candidates who stumble usually isn't Selenium syntax, it's the ability to reason about what a test case is actually protecting against, not just list steps. Practice explaining the "why" behind a test case out loud, not just the "what."

Get the reps in before the real thing

LastRoundAI's mock interview mode runs live technical rounds, Core Java, SQL, and system design included, with real follow-up questions instead of a static question bank, so you get practice defending an answer once an interviewer pushes back on it. The free plan includes 15 credits a month that reset monthly, and Starter is $19/mo if you want more reps before a Coforge loop.

Once you're through the technical prep, Auto-Apply queues tailored applications to services and IT roles like the ones Coforge is hiring for, 10 a month on the free plan, up to 400 a month on Ultimate, and nothing goes out until you approve it.

Questions about either product go to contact@lastroundai.com.

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 Coforge 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 Coforge 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 Coforge interview have?

Most Coforge 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 Coforge 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.

Leave a Reply

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