09 β Coding Questions, With Answers¶
Built from public sources: real reported interviews where they exist, plus curated question banks and real take-home examples. Every solution below is clean, correct, and aware of edge cases β and that is exactly what these rounds grade. They reward careful code, not clever tricks.
Honest note on BCE + how to calibrate¶
- Nobody has published "here are BCE's exact coding problems." What we know for sure about BCE: a multi-round process, EasyβMedium live coding, focus on "arrays and strings" and basics. The platform is usually HackerRank for the online assessment (OA), then a screen-share / CoderPad session in later rounds.
- Difficulty level: EasyβMedium. Truly hard DSA (data-structures-and-algorithms) is rare here β that is FAANG-tier stuff. What sets you apart is handling edge cases (empty input, Unicode, broken rows) and writing clean, production-style code, not fancy algorithms.
- Two kinds of problems to expect for this hybrid role:
- Classic SDET coding β string/list problems + pytest + API/Selenium (Sections B & C). This is most of the technical round.
- AI-eval coding β parsing JSON out of an LLM reply, retry/backoff, similarity metrics, a small LLM-as-judge or RAG-metric script (Section A). This is what earns you the "AI" part of the title.
- AI autocomplete is often turned off in live rounds, so practice typing these from memory.
How to behave in the round (and say it out loud): clarify the input and constraints β state your approach and its complexity β write the code β walk through an example β point out edge cases β say how you'd test it. Saying the edge cases out loud is what separates a "hire" from a "no hire" here.
Section A β AI/LLM-specific coding (the differentiator)¶
These give you the most value for the AI half of the role, so drill them first. Runnable versions live in practice-repo/coding_drills.py (checked by practice-repo/tests/test_coding_drills.py).
A1. Parse JSON from messy LLM output, validate, and retry β (the most common AI primitive)¶
Problem: An LLM is supposed to return JSON, but it wraps it in prose, wraps it in markdown code fences, or adds a trailing comma. Pull out the JSON and check it. If that fails, ask the model again β showing it the error β up to N times.
Remember: clean it, parse it, check required keys; if anything breaks, re-ask with the error attached.
import json, re
def extract_json(raw: str) -> dict:
"""Tolerantly pull a JSON object out of a noisy LLM response."""
raw = raw.strip()
# 1) strip ```json ... ``` fences
fence = re.search(r"```(?:json)?\s*(\{.*\})\s*```", raw, re.DOTALL)
if fence:
raw = fence.group(1)
# 2) grab the outermost {...}
start, end = raw.find("{"), raw.rfind("}")
if start == -1 or end == -1:
raise ValueError("no JSON object found")
snippet = raw[start:end + 1]
# 3) repair a common defect: trailing commas
snippet = re.sub(r",\s*([}\]])", r"\1", snippet)
obj = json.loads(snippet)
if not isinstance(obj, dict):
raise ValueError("parsed JSON is not an object")
return obj
def complete_json(call_llm, prompt, required_keys=(), max_retries=3) -> dict:
"""call_llm(prompt) -> str. Retries feeding the error back to the model."""
err = None
p = prompt + "\nReturn ONLY a valid JSON object."
for _ in range(max_retries):
raw = call_llm(p)
try:
obj = extract_json(raw)
missing = [k for k in required_keys if k not in obj]
if missing:
raise ValueError(f"missing keys: {missing}")
return obj
except (ValueError, json.JSONDecodeError) as e:
err = e
p = f"{prompt}\nYour last reply was invalid ({e}). Return corrected JSON only."
raise ValueError(f"no valid JSON after {max_retries} tries: {err}")
A2. Retry with exponential backoff + jitter¶
Problem: Wrap an API call so that when it fails with a 429 (too many requests) or a 5xx (server error), it retries β waiting 1s, then 2s, then 4s, and so on β with a cap on the wait and some randomness added.
Remember: each retry waits twice as long as the last (that's "exponential backoff"), and the random wait is the "jitter."
import random, time
def with_retry(fn, max_attempts=5, base=1.0, cap=30.0, retry_on=(Exception,)):
for attempt in range(max_attempts):
try:
return fn()
except retry_on:
if attempt == max_attempts - 1:
raise # exhausted -> surface the error
backoff = min(cap, base * (2 ** attempt))
time.sleep(random.uniform(0, backoff)) # "full jitter"
Retry-After header if the server sends one; and cap the total time spent, not just the number of attempts.
A3. Rate limiter (sliding-window) β a real recorded mock question¶
Problem: Write is_allowed(client_id, timestamp_ms). It returns False if that client has made more than N requests in the last T milliseconds.
Remember: keep each client's recent timestamps in a queue, drop the old ones, then check how many are left.
from collections import defaultdict, deque
class RateLimiter:
def __init__(self, max_requests: int, window_ms: int):
self.max = max_requests
self.window = window_ms
self.hits = defaultdict(deque) # client -> timestamps
def is_allowed(self, client_id: str, ts: int) -> bool:
q = self.hits[client_id]
while q and q[0] <= ts - self.window: # evict expired
q.popleft()
if len(q) < self.max:
q.append(ts)
return True
return False
max=0 should always deny (it does, since len(q) < 0 is never true); timestamps are assumed to only go forward; memory grows for every client seen (mention cleaning up stale clients with a TTL). Follow-ups: thread-safety (add a lock), a global requests-per-second cap, and the token-bucket approach as an alternative (it handles bursts more smoothly).
A4. Cosine similarity from scratch (asked at Amazon GenAI Center)¶
Remember: cosine similarity = how close two vectors point in the same direction. It's the dot product divided by both lengths.
import math
def cosine(a, b):
if len(a) != len(b):
raise ValueError("dim mismatch")
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a))
nb = math.sqrt(sum(y * y for y in b))
return 0.0 if na == 0 or nb == 0 else dot / (na * nb)
# NumPy version (say this is what you'd ship):
# import numpy as np
# def cosine(a, b): a,b=np.asarray(a),np.asarray(b); return float(a@b/(np.linalg.norm(a)*np.linalg.norm(b)))
A5. Top-k vector retrieval (RAG retrieval from scratch)¶
Remember: score every document against the query, sort by score, return the top k.
def top_k_retrieve(query_vec, corpus, k=3):
"""corpus: list[(doc_text, embedding)]. Returns k most similar docs."""
scored = [(cosine(query_vec, emb), doc) for doc, emb in corpus]
scored.sort(key=lambda x: x[0], reverse=True)
return [doc for _, doc in scored[:k]]
A6. Parse model outputs β compute accuracy (the core eval loop)¶
Problem: A classifier LLM replies in free text like "This looks like a BUG to me.". Pull the label out of an allowed set, then compare against the correct ("gold") labels to get accuracy.
Remember: if the text contains exactly one allowed label, use it; zero or more than one means it's unparseable.
ALLOWED = {"bug", "feature", "question"}
def parse_label(text: str):
text = text.lower()
found = [lab for lab in ALLOWED if re.search(rf"\b{lab}\b", text)]
return found[0] if len(found) == 1 else None # None = unparseable/ambiguous
def accuracy(predictions, golds):
assert len(predictions) == len(golds)
if not golds:
return 0.0
correct = sum(1 for p, g in zip(predictions, golds) if p == g)
return correct / len(golds)
A7. Mini LLM-as-judge + RAGAS faithfulness (ties to files 02 & the practice repo)¶
Faithfulness = supported claims / total claims β this is the hallucination gate:
Remember: break the answer into claims, then count how many the context actually backs up.
def split_claims(text):
return [s.strip() for s in re.split(r"[.!?]", text) if s.strip()]
def faithfulness(answer, context, is_supported):
"""is_supported(claim, context)->bool. In prod that's an LLM/NLI call."""
claims = split_claims(answer)
if not claims:
return 0.0
return sum(is_supported(c, context) for c in claims) / len(claims)
practice-repo/judge.py; faithfulness/recall/precision are in practice-repo/rag_metrics.py. Be ready to open those files and explain them line by line.
Section B β Python fundamentals / DSA-lite (the must-know 8 + extras)¶
These are sure-thing warm-ups in any SDET round. Expect two follow-ups on each: "now do it without the built-in" and "what's the time complexity?"
B1. Reverse a string (then without slicing)¶
def reverse(s): return s[::-1] # Pythonic
def reverse_manual(s): # "without slicing"
chars = list(s)
i, j = 0, len(chars) - 1
while i < j:
chars[i], chars[j] = chars[j], chars[i]
i, j = i + 1, j - 1
return "".join(chars)
# O(n) time, O(n) space.
B2. Palindrome check¶
def is_palindrome(s):
s = "".join(ch.lower() for ch in s if ch.isalnum()) # ignore case/punctuation
return s == s[::-1]
B3. Character / word frequency¶
from collections import Counter
def char_freq(s): return dict(Counter(s))
def word_freq(s): return dict(Counter(s.lower().split()))
# "without Counter":
def char_freq_manual(s):
d = {}
for ch in s:
d[ch] = d.get(ch, 0) + 1
return d
B4. Anagram check (reported verbatim on Glassdoor)¶
Remember: two words are anagrams if their sorted letters match.
def is_anagram(a, b):
norm = lambda x: sorted(x.lower().replace(" ", ""))
return norm(a) == norm(b) # O(n log n)
# O(n) version: Counter(a) == Counter(b)
B5. Remove / find duplicates (preserve order)¶
def dedupe(lst): return list(dict.fromkeys(lst)) # order-preserving, O(n)
def find_dupes(lst):
seen, dupes = set(), set()
for x in lst:
(dupes if x in seen else seen).add(x)
return list(dupes)
B6. Second largest (no sort)¶
def second_largest(nums):
first = second = float("-inf")
for n in nums:
if n > first:
first, second = n, first
elif first > n > second:
second = n
return None if second == float("-inf") else second
B7. Reverse words in a sentence (two variants)¶
def reverse_word_order(s): return " ".join(s.split()[::-1]) # "a b c" -> "c b a"
def reverse_each_word(s): return " ".join(w[::-1] for w in s.split()) # "ab cd"->"ba dc"
B8. FizzBuzz (screening warm-up)¶
def fizzbuzz(n):
for i in range(1, n + 1):
print("FizzBuzz" if i % 15 == 0 else
"Fizz" if i % 3 == 0 else
"Buzz" if i % 5 == 0 else i)
Extras commonly seen¶
def two_sum(nums, target): # O(n) hashmap β "the dict knowledge probe"
seen = {}
for i, n in enumerate(nums):
if target - n in seen:
return [seen[target - n], i]
seen[n] = i
return []
def count_vowels(s): return sum(ch in "aeiou" for ch in s.lower())
def missing_number(nums, n): # 1..n, one missing
return n * (n + 1) // 2 - sum(nums) # or XOR trick
def first_non_repeating(s):
c = Counter(s)
return next((ch for ch in s if c[ch] == 1), None)
Section C β Test-automation coding¶
C1. pytest β fixtures, parametrize, exceptions, mocking¶
import pytest
from unittest.mock import patch
# Fixture with setup/teardown via yield
@pytest.fixture
def api_client():
client = {"session": "open"} # setup
yield client
client["session"] = "closed" # teardown (runs after test)
# Parametrize across input sets β the canonical ask
@pytest.mark.parametrize("a,b,expected", [(2, 3, 5), (-1, 1, 0), (0, 0, 0)])
def test_add(a, b, expected):
assert a + b == expected
# Assert an exception is raised
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
1 / 0
# Mock an external call (senior differentiator)
def test_fetch_user():
with patch("requests.get") as mock_get:
mock_get.return_value.json.return_value = {"id": 1, "name": "Ada"}
# ... call the function under test that uses requests.get ...
mock_get.assert_called_once()
function/module/session, i.e. how long a fixture stays alive), conftest.py for sharing fixtures across files, and @pytest.mark for grouping tests. AI crossover: "build a pytest-based eval with DeepEval" β its metrics plug in as pytest assertions (faithfulness/hallucination). That sits right at the Pytest + LLM-eval overlap this role wants.
C2. API testing β Python requests + schema validation¶
import requests
from jsonschema import validate
def test_get_user_status_and_schema():
r = requests.get("https://reqres.in/api/users/2")
assert r.status_code == 200
body = r.json()
schema = {
"type": "object",
"properties": {"data": {"type": "object",
"properties": {"id": {"type": "number"}, "email": {"type": "string"}},
"required": ["id", "email"]}},
"required": ["data"],
}
validate(instance=body, schema=schema) # raises on mismatch
def test_request_chaining():
token = requests.post(".../login", json={"u": "x", "p": "y"}).json()["token"]
r = requests.get(".../profile", headers={"Authorization": f"Bearer {token}"})
assert r.status_code == 200
C3. RestAssured (Java) β your existing strength, expect this verbatim¶
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
// hit endpoint, validate status code + a response field
given()
.baseUri("https://reqres.in")
.when()
.get("/api/users/2")
.then()
.statusCode(200)
.body("data.id", equalTo(2))
.body("data.email", containsString("@"));
// POST with payload, assert echoed fields
given().contentType("application/json")
.body("{\"name\":\"morpheus\",\"job\":\"leader\"}")
.when().post("/api/users")
.then().statusCode(201).body("name", equalTo("morpheus"));
C4. Selenium (Java) β custom wait + screenshot-on-failure (reported at Swiggy; matches your saved coverage goals)¶
// Explicit/custom wait for a dynamic element
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement el = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("result")));
// Handle a dropdown
new Select(driver.findElement(By.id("country"))).selectByVisibleText("Canada");
// Screenshot on test failure via TestNG listener
public class ScreenshotListener implements ITestListener {
@Override public void onTestFailure(ITestResult result) {
File src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
try { FileUtils.copyFile(src, new File("screenshots/" + result.getName() + ".png")); }
catch (IOException e) { e.printStackTrace(); }
}
}
getWindowHandles() for multiple tabs, the Actions class for hover/drag, and an XPath to count table rows that contain a given word.
C5. Playwright (TS) β your other strength¶
import { test, expect } from '@playwright/test';
test('login and verify', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#user', 'ada');
await page.fill('#pass', 'secret');
await page.click('button[type=submit]');
await expect(page).toHaveTitle(/Dashboard/);
});
// Network intercept / block (great for deterministic tests)
await page.route('**/analytics', route => route.abort());
Section D β Data-processing coding (eval-scripting substrate)¶
Be ready to write these two ways: in plain Python (reading output files line by line matters for eval harnesses) and in pandas.
D1. CSV β pass-rate per category (the single most likely pattern for you)¶
# results.csv: category,expected,actual
import csv
from collections import defaultdict
def pass_rate_per_category(path):
total, passed = defaultdict(int), defaultdict(int)
with open(path, newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
cat = row["category"]
total[cat] += 1
if row["expected"].strip() == row["actual"].strip():
passed[cat] += 1
return {c: round(passed[c] / total[c], 3) for c in total}
# pandas one-liner:
# import pandas as pd
# df = pd.read_csv("results.csv")
# df["passed"] = df.expected.str.strip() == df.actual.str.strip()
# df.groupby("category")["passed"].mean()
chunksize to stream very large files instead of loading them all at once.
D2. Flatten nested JSON (reported as a real Amazon question)¶
Remember: walk the object; for nested dicts, join the keys with a separator so a inside b becomes a_b.
def flatten(obj, parent="", sep="_"):
items = {}
for k, v in obj.items():
key = f"{parent}{sep}{k}" if parent else k
if isinstance(v, dict):
items.update(flatten(v, key, sep))
else:
items[key] = v
return items
# {"a": {"b": 1, "d": {"e": 2}}} -> {"a_b": 1, "a_d_e": 2}
a_0_b).
D3. JSONL eval-results aggregation¶
Remember: JSONL = one JSON object per line. Read line by line and roll up the scores per label.
import json
from collections import defaultdict
def aggregate_jsonl(path):
"""Each line: {"id":..., "label":..., "score":...}. Roll up per label."""
sums, counts = defaultdict(float), defaultdict(int)
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
rec = json.loads(line)
sums[rec["label"]] += rec["score"]
counts[rec["label"]] += 1
return {lab: round(sums[lab] / counts[lab], 3) for lab in sums}
Section E β How these rounds run + a focused practice plan¶
Format: SDET β HackerRank OA (1 hr), then a screen-share live-coding session; OOP/SOLID + a framework-design discussion ("design a framework to test a login page," then live-code an addition to it). AI-eval β practical take-homes (48hβ3day), graded on whether it works + completeness + code quality, not on puzzles; live coding runs 45β120 min, often with AI autocomplete disabled.
Drill priority for your hybrid profile:
1. The must-know 8 (Section B) β these should be automatic.
2. AI primitives (A1 JSON+retry, A2 backoff, A3 rate limiter, A4 cosine, A6 parseβaccuracy) β these are your differentiators, and they're short.
3. pytest fixtures+parametrize+mock, and requests/RestAssured endpoint + schema validation (Section C).
4. CSVβpass-rate and flatten-JSON (Section D), in both plain Python and pandas.
5. Open the practice-repo and be able to explain judge.py, rag_metrics.py, llm_client.py line by line β that is "implement an LLM-as-judge / RAG metric," already done for you.
Mantra while coding: clarify β approach + Big-O β code β trace an example β edge cases β "and here's how I'd test it." Those last two phrases are what win you the hire.
β Runnable solutions: practice-repo/coding_drills.py Β· verify with pytest tests/test_coding_drills.py