Back to SDET

Testing Concepts & Exercises

Looking for JavaScript fundamentals (variables, promises, classes…)? They live in the JavaScript path.Go to JavaScript →
What are the types of test doubles?
A test double is any stand-in for a real component. Dummy: passed but never used. Stub: returns fixed canned values. Fake: a working but simplified implementation (e.g. an in-memory DB). Spy: records how it was called without changing behavior. Mock: a stub plus assertions on how it was called (e.g. sendEmail was called once).
Why and when do you mock — and when should you not?
Mock to isolate the unit under test from slow or unstable external dependencies (DB, third-party APIs, email), to simulate hard-to-reproduce errors like a 500, and to keep tests fast and deterministic. Don't mock when you specifically want to verify real integration, when a mock would diverge so far from reality it loses value, or when the real dependency is already local and fast.
How do you mock network requests in Playwright?
Use page.route to intercept a URL pattern and fulfill it with a canned response: page.route('**/api/products', r => r.fulfill({ status: 200, body: JSON.stringify(data) })). You can return a 500 to test error UI, or add a delay before route.continue() to test loading states — all without touching the real backend.
How do you structure a CRUD integration test for a REST API?
Cover the full lifecycle and the auth/validation edges: create (POST -> 201), read it back (GET -> 200), update partially (PATCH -> 200, unchanged fields stay), delete (DELETE -> 204), then confirm it's gone (GET -> 404). Add negative cases: no token -> 401, wrong role -> 403, missing required field -> 422, duplicate -> 409. Each test should set up its own data so it stays independent.
What is TTL and where does it matter in testing?
TTL (time to live) is how long something stays valid: auth tokens/sessions (JWT expiry), cache entries (Redis, CDN, HTTP Cache-Control), and rate-limit windows (X-RateLimit-Reset). As an SDET you test that an expired token is rejected, a cache entry is a miss after its TTL, and that requests beyond the limit are throttled and reset correctly.
Why avoid fixed sleeps, and what do you use instead?
A fixed sleep (waitForTimeout(3000)) is either too short (flaky) or too long (slow), because real timing varies. Use condition-based waits instead: waitForSelector for an element, expect(...).toBeVisible(), waitForResponse for an API call, or a generic polling helper that re-checks a condition until a timeout. This makes tests both faster and more reliable.
How do retries with exponential backoff work, and when should you not retry?
Retry re-runs a failing operation up to N attempts, waiting longer between each try (delay * 2^attempt) so a struggling service can recover without being hammered. Only retry transient failures (network blips, 5xx, timeouts). Don't retry deterministic client errors like a 404 or 422 — a shouldRetry predicate lets you skip those, since retrying will never succeed.
Do interviewers ask about AI tool usage, and how should a candidate respond?
Yes, in recent technical interviews — particularly for higher-paying roles — candidates are commonly asked whether they use AI tools, which tools they use, and how they use them. Interviewers evaluate not only whether AI is used, but whether it is used consciously and with genuine understanding of the output. A strong response is to explain that you use AI to accelerate tasks you already understand, and that you always review and comprehend the generated code or suggestions. Some take-home assignments now even require a dedicated file (e.g., AI_PROCESS.md) documenting how AI was used throughout the project.
You have a backend issue that occurs intermittently — it happens sometimes but not others. How would you approach diagnosing and resolving it?
Start by reviewing existing logs to look for patterns around when the issue occurs. If logging is not already in place, implement it on the relevant APIs and services to capture HTTP request/response data including status codes (e.g., 200 OK, 500 Server Error, 400 Bad Request). Use cloud monitoring tools such as AWS CloudWatch if your infrastructure supports it. Add try/catch blocks to capture and record exceptions at the point of failure. Then narrow down the scope: determine where in the system the problem originates and under what conditions it appears — such as time of day, traffic load, or specific input values. Finally, consider implementing retry logic to handle transient failures gracefully, since intermittent issues are often caused by temporary network or resource unavailability.
During a technical screening, you are shown a pseudocode function that iterates through an array, accumulates the sum of all elements, and then divides by the total count. What does the function compute, and what is its time complexity?
The function computes the arithmetic mean (average) of the elements in the array. Its time complexity is O(n), because the algorithm performs exactly one pass through all n elements to accumulate the sum, followed by a single constant-time division. The number of operations grows linearly with the size of the input.
What is the difference between checking set equality versus computing set intersection when comparing two arrays?
Set equality (e.g., set1.equals(set2) in Java) returns true only if both sets contain exactly the same elements — it is an all-or-nothing check. Set intersection (e.g., set1.retainAll(set2) in Java or set(a) & set(b) in Python) returns the subset of elements that appear in both collections. When the goal is to find which specific elements from one array exist in another, intersection is the correct operation. Equality is only appropriate when you need to verify that the two arrays contain the exact same unique values.
What is Test-Driven Development (TDD) and can it be applied to an existing legacy application?
TDD is a methodology where you write a failing test first, then write the minimum code to make it pass, then refactor. You cannot retroactively apply TDD to code that is already written, but you can adopt TDD discipline for all new work going forward. For legacy code, you apply TDD principles by adding characterisation tests before refactoring existing logic, creating a safety net without rewriting everything at once.
Write a recursive function to calculate the factorial of a number. What is the base case, and how do you handle edge cases?
A recursive factorial function needs two parts: 1. **Base case** (stops the recursion): when n equals 0 or 1, return 1, since 0! = 1! = 1. 2. **Recursive case**: return n multiplied by factorial(n - 1). Pseudocode / general form: function factorial(n): if (n == 0 || n == 1): return 1 else: return n * factorial(n - 1) Key points: - Always define the base case first to prevent infinite recursion (stack overflow). - Add a precondition for negative inputs (e.g., throw an error or return an error message), since factorial is undefined for negative numbers. - Time complexity: O(n) — one recursive call per decrement. - Each call is placed on the call stack following LIFO order; the results are multiplied as the stack unwinds.
Given two integer arrays, how would you find the elements that are common to both (i.e., which elements from the first array also appear in the second array)?
Several approaches exist depending on the language: **Java** – Convert both arrays to List objects and use retainAll(), which modifies the first list in place to keep only elements that also exist in the second: java import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); List<Integer> list2 = new ArrayList<>(Arrays.asList(3, 4, 5, 6, 7)); list1.retainAll(list2); System.out.println("Common elements: " + list1); // [3, 4, 5] } } **JavaScript** – Use filter() combined with includes(): javascript const array1 = [1, 2, 3, 4, 5]; const array2 = [3, 4, 5, 6, 7]; const common = array1.filter(element => array2.includes(element)); console.log(common); // [3, 4, 5] **Python** – Use set intersection: python def common_elements(a, b): return set(a) & set(b) Note: Converting to sets removes duplicates. If preserving duplicates from the original arrays is a requirement, use the filter-based approach instead.