Skip to content

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}")
Talking points: never trust the model's formatting. Validate the result against a schema (mention Pydantic for real code). Use a limited number of retries, and feed the error back so the model can fix itself. The code is safe to run more than once.

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"
Why jitter? If many clients fail at the same moment and all wait the same 4s, they retry together and slam the server again (this is the "thundering herd" problem). Random jitter spreads the retries out. Follow-up: only retry calls that are safe to repeat (idempotent); honor a 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
Edge cases to call out: 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)))
Why it matters: cosine similarity is the engine behind RAG retrieval, semantic-similarity eval metrics, and finding duplicates. Edge case: a zero vector β†’ return 0, never divide by zero.

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]]
Follow-up: at large scale you don't compare against every document one by one. You use an ANN index (approximate nearest neighbors, e.g. FAISS or a vector DB).

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)
Talking points: an ambiguous output (zero labels or several) is itself a kind of failure β€” count it, don't quietly pick one. Mention precision/recall per class as a step beyond plain accuracy, and the idea that a false positive and a false negative may not cost the same.

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)
A full, runnable LLM-as-judge (pointwise + pairwise with position-swap) already lives in 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]
Edge cases: an empty string counts as a palindrome; "A man, a plan, a canal: Panama" β†’ True once you strip the punctuation and case.

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
Edge cases: fewer than 2 distinct values β†’ None; duplicates of the max are handled correctly.

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()
Know these: fixture scopes (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(); }
    }
}
Selenium follow-ups often asked: the types of waits (implicit vs explicit vs fluent), 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()
Edge cases to mention: broken or too-short rows, missing values, text where you expected a number, file encoding, and using 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}
Follow-up: recursive vs iterative (recursion can blow the stack on very deeply nested data); and how you'd handle lists (put the index in the key, like 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