American Express (AMEX) Online Assessment Experience (2026) | 3 Coding Questions
#greedy
#interview-experience
#american-express-company
#online-assessment
A few days ago, I appeared for the American Express (AMEX) Online Assessment as part of the hiring process. Since I couldn't find many recent OA experiences online, I thought I'd share mine in detail. Hopefully, this gives future candidates a realistic idea of what to expect.
Unlike many online assessments that focus heavily on tricky algorithms, this one leaned much more towards implementation quality. None of the questions looked impossible at first glance, but writing clean, bug-free code within the given time was the real challenge. I managed to clear the Online Assessment and move on to the next round.
Interview Timeline
- Company: American Express (AMEX)
- Round: Online Assessment
- Mode: Online
- Questions: 3 Coding Problems
- Focus Areas: Greedy, Simulation, Geometry, Implementation
- Result: Cleared the OA
One thing I noticed was that every question required careful reading. It was very easy to misunderstand a small detail and end up solving the wrong problem.
Question 1 : Minimum Moves to Spread Stones
The first problem was based on a 3 × 3 grid containing stones. Some cells already contained multiple stones, while others were empty. The objective was to move stones only to adjacent cells until every cell contained exactly one stone, while minimizing the total number of moves.
If you've solved LeetCode 2850 - Minimum Moves to Spread Stones Over Grid, this problem will immediately feel familiar.
My Initial Thought Process
At first glance, it looked like a graph problem. My first instinct was to perform BFS from every overloaded cell toward empty cells.
After thinking for a few minutes, I realized the grid size is fixed (only 9 cells), which makes brute-force matching completely feasible. Instead of searching repeatedly, the problem becomes matching surplus stones with empty cells while minimizing Manhattan distance.
My Approach
- Store every empty cell.
- Store every extra stone (if a cell has 3 stones, add it twice).
- Try every possible assignment.
- Compute total Manhattan distance.
- Return the minimum.
Since the grid size never changes, the search space remains very small.
C# Solution
public class Solution
{
int answer = int.MaxValue;
public int MinimumMoves(int[][] grid)
{
List<int[]> extra = new();
List<int[]> empty = new();
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (grid[i][j] == 0)
empty.Add(new[] { i, j });
while (grid[i][j] > 1)
{
extra.Add(new[] { i, j });
grid[i][j]--;
}
}
}
DFS(0, extra, empty);
return answer;
}
void DFS(int index, List<int[]> extra, List<int[]> empty)
{
if (index == extra.Count)
{
int cost = 0;
for (int i = 0; i < extra.Count; i++)
{
cost += Math.Abs(extra[i][0] - empty[i][0]);
cost += Math.Abs(extra[i][1] - empty[i][1]);
}
answer = Math.Min(answer, cost);
return;
}
for (int i = index; i < empty.Count; i++)
{
(empty[index], empty[i]) = (empty[i], empty[index]);
DFS(index + 1, extra, empty);
(empty[index], empty[i]) = (empty[i], empty[index]);
}
}
}
Understanding the DFS Approach
The DFS() function does not physically move stones. Instead, it generates every possible assignment of extra stones to empty cells.
Think of it this way:
extra[i]represents the i-th extra stone that needs to be moved.empty[i]represents the destination assigned to that stone.- By generating every permutation of the
emptylist, we try every possible way of matching extra stones with empty cells. - For each complete assignment, we calculate the total Manhattan distance (minimum moves) and keep the smallest value.
Example
// Extra Stones
Index Stone
0 E1
1 E2
2 E3
// Empty Cells
Index Cell
0 A
1 B
2 C
Initially, the assignment is:
- E1 → A
- E2 → B
- E3 → C
However, this may not be the optimal assignment. The DFS generates every possible mapping.
DFS(0)
┌────────────┼────────────┐
│ │ │
A B C B A C C B A
E1→A E1→B E1→C
DFS(1) DFS(1) DFS(1)
┌──────┐ ┌──────┐ ┌──────┐
│ │ │ │ │ │
A B C A C B B A C B C A C B A C A B
E2→B E2→C E2→A E2→C E2→B E2→A
↓ ↓ ↓
DFS(2) DFS(2) DFS(2)
↓ ↓ ↓
Compute the total Manhattan distance for each complete assignment.
All Permutations Generated
| Empty Cell Order | Stone Assignment |
|---|---|
| A B C | E1 → A, E2 → B, E3 → C |
| A C B | E1 → A, E2 → C, E3 → B |
| B A C | E1 → B, E2 → A, E3 → C |
| B C A | E1 → B, E2 → C, E3 → A |
| C A B | E1 → C, E2 → A, E3 → B |
| C B A | E1 → C, E2 → B, E3 → A |
How the Backtracking Works
At each recursion level:
- We fix the destination for one extra stone.
- We swap the current position with every remaining position in the
emptylist. - This generates a new assignment for the current stone.
- We recursively assign destinations for the remaining stones.
- After returning, we swap back (backtracking) so the next permutation can be explored.
When all extra stones have been assigned (index == extra.Count), we:
- Compute the total Manhattan distance for the current assignment.
- Compare it with the minimum answer found so far.
- Update the answer if the current assignment requires fewer moves.
This is a classic backtracking permutation generation technique where every possible mapping between extra stones and empty cells is explored to find the minimum total movement cost.
Time Complexity
- O(E!) where E ≤ 8, making it practical.
Question 2 : Rectangle Covering Maximum Points
This was the most interesting question of the assessment. We were given:
- A set of coordinate points( Constraints N ≤ 200 or N ≤ 500 , N number of coordinate points).
- A fixed rectangle perimeter P.
- The task was to place an axis-aligned rectangle (its sides are parallel to the X and Y axes) whose perimeter equals P, such that it contains the maximum number of given points. Points lying on the rectangle's boundary were also considered inside the rectangle.
The goal was to return the maximum number of points that could be enclosed by such a rectangle.
I couldn't find an exact LeetCode equivalent for this problem.
My Thought Process
My initial idea was to try every possible rectangle independently, but that quickly became impractical because both the rectangle's dimensions and its position could vary.
After analyzing the problem, I noticed an important mathematical observation:
2 × (width + height) = P
which simplifies to
width + height = P / 2
This significantly reduces the search space. Instead of considering arbitrary rectangle dimensions, we only need to enumerate every valid (width, height) pair satisfying this equation.
The next observation was that an optimal rectangle can always be shifted until its left edge aligns with the x-coordinate of some point and its bottom edge aligns with the y-coordinate of some point, without decreasing the number of enclosed points. Therefore, instead of checking every possible position, it is sufficient to use the given point coordinates as candidate left and bottom edges.
My Approach
For every valid (width, height) pair:
- Iterate through every point's x-coordinate as the rectangle's left edge.
- Iterate through every point's y-coordinate as the rectangle's bottom edge.
- Construct the rectangle:
- Left = x
- Right = x + width
- Bottom = y
- Top = y + height
- Count how many points lie inside or on the boundary of the rectangle.
- Update the maximum count.
public class Solution
{
public int MaxPointsInsideRectangle(int[][] points, int perimeter)
{
int n = points.Length;
int answer = 0;
int half = perimeter / 2;
// Enumerate every possible (width, height)
for (int width = 0; width <= half; width++)
{
int height = half - width;
// Try every x-coordinate as the left edge
for (int i = 0; i < n; i++)
{
int left = points[i][0];
// Try every y-coordinate as the bottom edge
for (int j = 0; j < n; j++)
{
int bottom = points[j][1];
int right = left + width;
int top = bottom + height;
int count = 0;
// Count points inside rectangle
for (int k = 0; k < n; k++)
{
int x = points[k][0];
int y = points[k][1];
if (x >= left && x <= right &&
y >= bottom && y <= top)
{
count++;
}
}
answer = Math.Max(answer, count);
}
}
}
return answer;
}
}
public class Program
{
public static void Main()
{
int[][] points =
{
new[] {1, 1},
new[] {2, 3},
new[] {3, 2},
new[] {4, 4},
new[] {5, 2}
};
int perimeter = 8;
Solution solution = new Solution();
int result = solution.MaxPointsInsideRectangle(points, perimeter);
Console.WriteLine("Maximum Points Inside Rectangle = " + result);
// Output : Maximum Points Inside Rectangle = 2
}
}
Coordinate Diagram
Y
5 |
4 | ● (4,4)
3 | ● (2,3)
| +-------------------------+
2 | | ● (3,2) | ● (5,2)
1 | ● (1,1) |
| +-------------------------+
0 +--------------------------------------------------> X
0 1 2 3 4 5 6
Question 3 : Pizza Discounts
The final problem wasn't algorithmically difficult. Instead, it tested software engineering and implementation skills.
A pizza store offers multiple discount schemes. Given a list of pizza orders, calculate the minimum amount the customer has to pay after applying exactly one of the available discounts. Each pizza has:
- Name
- Size (Small, Medium, Large)
- Type (Veg / Non-Veg)
- Price
- Discount Rules
The store offers the following four discounts:
Discount 1: Buy 2 Get 1 Free
- For every 3 pizzas purchased, the cheapest pizza is free.
- If there are more than 3 pizzas, the offer can be applied multiple times.
Discount 2: Veg Special
- If the order contains at least 2 Veg pizzas, get 20% off on the total price of Veg pizzas.
Discount 3: Large Pizza Offer
- Every Large pizza receives a ₹100 discount.
Discount 4: Flat Bill Discount
- If the total bill is ₹1500 or more, receive ₹200 off.
The customer can apply only one discount, whichever results in the lowest final bill. Return the minimum payable amount.
Example
Input
Margherita Medium Veg ₹300
Farmhouse Large Veg ₹500
Pepperoni Large NonVeg ₹700
Cheese Small Veg ₹250
Discounts
Buy2Get1 = 1500
Veg20% = 1540
Large Offer = 1550
Flat Discount = 1550
Total Bill
300 + 500 + 700 + 250 = 1750
Discount Comparison
| Discount Rule | Calculation | Discount | Final Bill |
|---|---|---|---|
| Buy 2 Get 1 Free | 4 pizzas purchased → Cheapest pizza (₹250) is free | ₹250 | ₹1500 ✅ |
| Veg Special (20%) | Veg pizzas = ₹300 + ₹500 + ₹250 = ₹1050 → 20% of ₹1050 | ₹210 | ₹1540 |
| Large Pizza Offer | 2 Large pizzas × ₹100 discount | ₹200 | ₹1550 |
| Flat Bill Discount | Total bill = ₹1750 ≥ ₹1500 → Flat ₹200 discount | ₹200 | ₹1550 |
Final Output : 1500
My Approach
Instead of putting all discount logic into a single method, implement each discount independently.
Calculate Total Bill
│
▼
Discount 1
Discount 2
Discount 3
Discount 4
│
▼
Take Minimum
This keeps the code modular, readable and easy to extend if additional discounts are introduced.
using System;
using System.Collections.Generic;
using System.Linq;
public class Pizza
{
public string Name { get; set; }
public string Size { get; set; }
public string Type { get; set; }
public decimal Price { get; set; }
public Pizza(string name, string size, string type, decimal price)
{
Name = name;
Size = size;
Type = type;
Price = price;
}
}
public class DiscountCalculator
{
public decimal GetMinimumBill(List<Pizza> pizzas)
{
decimal total = pizzas.Sum(p => p.Price);
decimal d1 = Buy2Get1Free(pizzas, total);
decimal d2 = VegDiscount(pizzas, total);
decimal d3 = LargePizzaDiscount(pizzas, total);
decimal d4 = FlatDiscount(total);
return Math.Min(Math.Min(d1, d2), Math.Min(d3, d4));
}
// Discount 1
private decimal Buy2Get1Free(List<Pizza> pizzas, decimal total)
{
var prices = pizzas.Select(p => p.Price)
.OrderBy(p => p)
.ToList();
decimal free = 0;
for (int i = 0; i < prices.Count / 3; i++)
free += prices[i];
return total - free;
}
// Discount 2
private decimal VegDiscount(List<Pizza> pizzas, decimal total)
{
var veg = pizzas.Where(p => p.Type == "Veg").ToList();
if (veg.Count < 2)
return total;
decimal vegTotal = veg.Sum(p => p.Price);
return total - (vegTotal * 0.20m);
}
// Discount 3
private decimal LargePizzaDiscount(List<Pizza> pizzas, decimal total)
{
int large = pizzas.Count(p => p.Size == "Large");
return total - large * 100;
}
// Discount 4
private decimal FlatDiscount(decimal total)
{
if (total >= 1500)
return total - 200;
return total;
}
}
public class Program
{
public static void Main()
{
List<Pizza> pizzas = new List<Pizza>
{
new Pizza("Margherita","Medium","Veg",300),
new Pizza("Farmhouse","Large","Veg",500),
new Pizza("Pepperoni","Large","NonVeg",700),
new Pizza("Cheese","Small","Veg",250)
};
DiscountCalculator calculator = new DiscountCalculator();
decimal bill = calculator.GetMinimumBill(pizzas);
Console.WriteLine($"Minimum Payable Amount = ₹{bill}");
}
}
Overall Difficulty
| Question | Topic | Difficulty |
|---|---|---|
| Q1 | Greedy / Backtracking | ⭐⭐⭐☆☆ (3.5/5) |
| Q2 | Geometry + Mathematical Observation | ⭐⭐⭐⭐☆ (4.5/5) |
| Q3 | Simulation / Object-Oriented Implementation | ⭐⭐⭐☆☆ (3/5) |
Overall Difficulty: 7/10
The assessment wasn't about advanced algorithms. Instead, it focused on implementation quality, careful reading of problem statements and writing clean, bug-free code under time pressure.
- Q1 looked like a graph problem initially, but once the small search space was identified, the solution became straightforward.
- Q2 was the toughest problem because there was no standard pattern to apply. The key was discovering the mathematical observation that reduced the search space.
- Q3 involved the most coding, but the logic itself was simple. Success depended more on code organization and handling multiple business rules than on algorithmic complexity.
Mistakes I Made
Looking back, there were a couple of things I could have done better.
-
For the second problem, I initially spent too much time searching for a familiar pattern instead of carefully analyzing the constraints. Once I stopped trying to map it to a known LeetCode problem and focused on the mathematical observation behind the perimeter, the solution became much clearer.
-
The third question reminded me how important clean code organization is during online assessments. Writing everything inside a single function would have become messy very quickly. Splitting the logic into small helper methods made debugging much easier.
-
One lesson I took away is that implementation-heavy problems reward discipline more than clever tricks. A simple solution with well-structured code often performs better than an over-engineered one.
What This OA Really Tested
After completing all three questions, I felt the assessment wasn't primarily about memorizing algorithms. Instead, it evaluated whether you can:
- Read long problem statements carefully.
- Convert business requirements into code.
- Keep your implementation clean.
- Handle multiple edge cases without introducing bugs.
- Stay calm even when the solution requires a lot of coding.
Those are exactly the kinds of skills engineers use every day in real projects.
Final Thoughts
Overall, I enjoyed this Online Assessment. It felt different from many coding rounds that rely heavily on well-known LeetCode patterns. Here, the emphasis was on translating ideas into working code and paying attention to implementation details.
If you're preparing for future American Express Online Assessments, my advice would be to go beyond solving algorithmic problems. Practice implementation-heavy questions, simulation problems and object-oriented coding exercises. Those skills can make a significant difference.
Verdict: ✅ Passed to the Next Round
I hope this experience helps future candidates. Best of luck with your preparation!
