A recurring detail in CGI interview writeups from 2021 through 2024: the first gate for campus and entry-level hiring is almost always an AMCAT-administered online test covering aptitude, logical reasoning, English, and core CS topics, paired with two programming questions, not a single leetcode-style round but a bundled aptitude-plus-code screen (GeeksforGeeks candidate report, 2019). Clear that and the path splits by role: associate and campus hires typically face one technical round built around OOP basics, a resume walkthrough, and "very basic" MySQL queries, in one candidate's own words, followed by an HR round that's closer to a logistics conversation than a grilling.
Experienced hires, especially anyone interviewing against a Java, Spring Boot, or Microservices job description, get a longer loop. One 2021 account describes three rounds in a single day: a 45-minute round covering string manipulation without built-in methods plus Java 8 streams and lambdas, a second 45-minute round built around a REST API design exercise, and a short domain-lead round focused on project architecture rather than new material (GeeksforGeeks, CGI interview experience 2021). Specialist roles diverge further still. A candidate interviewing for an AI/ML Architect position reported a single technical round in front of a three-person panel that asked almost nothing about coding and everything about algorithm selection, Azure migration, and neural network fundamentals (GeeksforGeeks, CGI AI/ML Architect interview).
One thing worth flagging honestly: at least one candidate reported clearing every round and completing onboarding paperwork before the offer was pulled without a clear explanation. That's a single account, not a pattern we can confirm at scale, but it's a reason to keep your other applications moving even after a strong CGI loop. This page groups the real, reported rounds, online assessment, coding, core CS fundamentals, SQL, Java/Spring Boot for experienced hires, cloud and AI/ML domain questions for specialist roles, project deep dives, and the managerial and HR rounds, into roughly 50 questions with original answers.
Online assessment: AMCAT aptitude plus two coding questions
Campus and associate-level candidates consistently describe an AMCAT test bundling aptitude, logical reasoning, English, and a computer-science module ahead of two programming problems. Reasoning and English questions aren't specific to CGI, so this section sticks to the CS and coding pieces candidates actually mention.
Easy questions
19Bundled screens like this exist to filter volume before any human time gets spent, and CGI hires at high volume for campus and associate roles across multiple countries. Aptitude and reasoning sections are cheap to grade automatically and correlate loosely with the kind of structured thinking a coding interview also tests, so they act as a first-pass filter before the two programming questions do the more direct work of checking whether you can actually write correct code under a timer.
The practical takeaway if you're prepping: don't skip the aptitude section thinking it doesn't matter. Some AMCAT-style assessments apply a minimum cutoff per section, so a strong coding score doesn't always offset a weak aptitude score.
Candidate reports describe these as beginner to intermediate, closer to what you'd find in an easy-to-medium data structures set than a hard algorithmic puzzle. Expect array manipulation, string processing, or basic pattern printing rather than graph algorithms or dynamic programming with multiple states.
That doesn't mean you should under-prepare. The bar for these questions is usually correctness and clean handling of edge cases, empty input, single-element arrays, more than clever time complexity. A working, readable brute-force solution beats an elegant one with an off-by-one bug.
Two pointers, one at each end, swapping characters and moving inward until they cross. It's O(n) time and O(1) extra space if you're working with a mutable character array, which is the version interviewers usually want to see you reason through out loud rather than just call a library function.
void reverse(char[] s) {
int left = 0, right = s.length - 1;
while (left < right) {
char tmp = s[left];
s[left] = s[right];
s[right] = tmp;
left++;
right--;
}
}If the string type in your language is immutable, like Java's String, be upfront that you'd convert to a char array or StringBuilder first. Interviewers care whether you know why that step is necessary, not just that you memorized the swap logic.
Normalize first, lowercase everything and strip anything that isn't a letter or digit, then run the same two-pointer comparison used for reversal, except this time you compare instead of swap.
boolean isPalindrome(String s) {
String clean = s.toLowerCase().replaceAll("[^a-z0-9]", "");
int left = 0, right = clean.length() - 1;
while (left < right) {
if (clean.charAt(left) != clean.charAt(right)) return false;
left++;
right--;
}
return true;
}The normalization step is the part candidates skip under time pressure, and it's usually the part the interviewer is actually testing for. A solution that only handles clean, single-word input misses half the point of the question.
Pattern-printing questions show up often enough in CGI's coding round that it's worth having the mental model down cold, not just the specific pattern. The core idea is always two nested loops: the outer loop controls the row, the inner loop controls how many characters print on that row, usually as some function of the row number.
void printTriangle(int n) {
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
System.out.print("*");
}
System.out.println();
}
}Once you can explain that the inner loop's bound is a function of i, generalizing to a pyramid, a diamond, or a number pattern is just changing that function and adding leading spaces where needed. Say that out loud during the interview instead of silently re-deriving each variant from scratch.
The two-sum problem. Brute force is checking every pair, O(n squared). The interview-quality answer uses a hash map: walk the array once, and for each number check whether target minus that number already exists in the map before inserting the current number.
int[] twoSum(int[] nums, int target) {
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);
}
return new int[]{-1, -1};
}Checking before inserting matters if duplicates or a target that equals double a single element are in play. Insert first and you can accidentally match an element with itself.
Two strings are anagrams if they contain the same characters with the same frequencies. Sort both and compare, O(n log n), or build a frequency count for each and compare the counts, O(n). The frequency-count version is the one to lead with if asked to optimize.
boolean isAnagram(String a, String b) {
if (a.length() != b.length()) return false;
int[] count = new int[256];
for (char c : a.toCharArray()) count[c]++;
for (char c : b.toCharArray()) count[c]--;
for (int n : count) if (n != 0) return false;
return true;
}Encapsulation bundles data and the methods that operate on it, hiding internal state behind a controlled interface, a BankAccount class exposing deposit and withdraw but not letting you set balance directly. Abstraction hides implementation complexity behind a simpler interface, a PaymentProcessor interface that a caller uses without knowing whether it's calling Stripe or a bank API underneath. Inheritance lets a class reuse and extend another's behavior, a SavingsAccount extending Account and adding interest calculation. Polymorphism lets different classes respond to the same method call in their own way, calling calculateInterest() on a list of mixed Account subtypes and getting the right behavior for each.
Interviewers usually push past the definitions to a follow-up like "where did you actually use inheritance versus composition in a real project," so have a concrete answer ready rather than restating the definition a second time.
Overloading is having multiple methods with the same name but different parameter lists in the same class, resolved at compile time based on the arguments you pass. Overriding is a subclass providing its own implementation of a method already defined in its parent, resolved at runtime based on the actual object type.
class Calculator {
int add(int a, int b) { return a + b; } // overload 1
double add(double a, double b) { return a + b; } // overload 2
}
class Base {
void speak() { System.out.println("Base"); }
}
class Derived extends Base {
@Override
void speak() { System.out.println("Derived"); } // override
}The compile-time versus runtime distinction is the part interviewers actually want. Overloading is static binding, overriding is dynamic binding through the virtual method table.
The most portable version uses a subquery with LIMIT and OFFSET, or DISTINCT plus a self-comparison if duplicates in salary need to be treated as one value.
SELECT MAX(salary) AS second_highest
FROM Employee
WHERE salary < (SELECT MAX(salary) FROM Employee);This version handles duplicates correctly, since it compares against the actual maximum rather than relying on row position. A LIMIT 1 OFFSET 1 approach can misbehave if the top salary appears in more than one row.
INNER JOIN returns only rows that have a match in both tables. LEFT JOIN returns every row from the left table, filling in NULLs for any columns from the right table where no match exists. RIGHT JOIN is the mirror image, every row from the right table, NULLs for unmatched left-side columns.
SELECT e.name, d.department_name
FROM Employee e
LEFT JOIN Department d ON e.dept_id = d.id;
-- returns every employee, even ones with no matching department rowWHERE filters individual rows before any grouping happens. HAVING filters groups after a GROUP BY has already aggregated rows, so it's the clause you use to filter on an aggregate value like a count or a sum.
SELECT department_id, COUNT(*) AS emp_count
FROM Employee
WHERE status = 'active'
GROUP BY department_id
HAVING COUNT(*) > 5;Trying to filter on COUNT(*) in a WHERE clause fails, since WHERE runs before the aggregation exists yet. That's usually the exact mistake this question is fishing for.
A primary key uniquely identifies each row in a table and can't be null. A foreign key is a column, or set of columns, in one table that references the primary key of another table, enforcing referential integrity between the two.
A table can have only one primary key, though that key can span multiple columns as a composite key. A table can have multiple foreign keys, one for each relationship it participates in.
Streams let you express a filter-then-transform pipeline declaratively instead of manually managing a loop counter and an output list. The stream describes what should happen to each element, filter, map, collect, rather than how to iterate.
List<String> names = employees.stream()
.filter(e -> e.getSalary() > 50000)
.map(Employee::getName)
.collect(Collectors.toList());Worth mentioning unprompted: streams are lazy. Nothing in the filter or map stage actually runs until a terminal operation like collect or forEach triggers evaluation, which is why you can chain several intermediate operations without paying for a separate pass through the list at each step.
A neural network is a function built from layers of simple weighted calculations, stacked so that each layer learns to represent the input data in a slightly more useful way for the final task than the layer before it. Feed it an image, and early layers might respond to edges and simple shapes, later layers combine those into recognizable parts, and the final layer maps that representation to a decision, a label, a probability, a number.
The weights that decide what each layer responds to aren't hand-coded, they're learned by adjusting them repeatedly to reduce the gap between the network's predictions and known correct answers on training data, a process called backpropagation.
Interviewers use this question to see whether you actually made the decisions you're describing or inherited them from a team lead without understanding the trade-off. A strong answer names a specific alternative you considered and rejected, and the concrete reason, not just what you built.
If you genuinely didn't make many architectural calls on the project, that's fine to say directly. Pick the smallest real decision you did own, why you chose a particular library, how you structured error handling, and explain it with the same level of detail you'd give a bigger one. A specific small answer beats a vague big one every time.
A genuine answer here connects something specific about consulting-style delivery, working across multiple client environments and domains rather than one product for years, to something you've actually valued or wanted more of in your own career, breadth of exposure, client-facing responsibility, working across a mix of legacy and modern systems.
Avoid a generic answer about company size or reputation alone. A domain lead running this round has heard "I want to work for a stable, established company" a hundred times, and it doesn't tell them anything about whether you'll be good in a consulting environment specifically, where requirements, stakeholders, and codebases change from client to client far more than they would inside a single product company.
Answer both plainly and be consistent with whatever you said earlier in the process. If your notice period genuinely depends on something, an ongoing project handover, a bonus payout date, say so specifically rather than giving a vague range that sounds like you're keeping options open.
Always have at least one real question ready, and make it specific to what you've actually heard in the earlier rounds rather than something generic pulled from a template. Asking about the specific client or domain the team you'd join actually supports, or how project assignments get decided for someone new, signals you were listening in the technical rounds, not just executing a script.
One account of a candidate who cleared every round, including documentation, only to be rejected without explanation during onboarding is a useful reminder here: it's fair to ask directly what the remaining steps are after this round and roughly when to expect a final decision, rather than assuming a verbal "you did well" means the offer is locked in.
Medium questions
22A single pass to build a frequency count, keyed by character, then a second pass over the original string in order, returning the first character whose count is exactly one. Two passes, O(n) time, O(1) extra space if the character set is fixed, like ASCII lowercase letters.
char firstNonRepeating(String s) {
int[] freq = new int[256];
for (char c : s.toCharArray()) freq[c]++;
for (char c : s.toCharArray()) {
if (freq[c] == 1) return c;
}
return '