{"id":2031,"date":"2026-07-22T00:00:00","date_gmt":"2026-07-21T18:30:00","guid":{"rendered":"https:\/\/lastroundai.com\/blog\/?post_type=iq&#038;p=2031"},"modified":"2026-07-21T21:04:47","modified_gmt":"2026-07-21T15:34:47","slug":"sdet","status":"publish","type":"iq","link":"https:\/\/lastroundai.com\/interview-questions\/sdet","title":{"rendered":"SDET Interview Questions (2026): Most Asked, With Answers"},"content":{"rendered":"<p>A candidate at a mid-size logistics company got asked last year to explain why a Selenium test kept failing intermittently on a button that clicked fine every single time a human tried it by hand. He&#8217;d used <code>Thread.sleep(3000)<\/code> everywhere in his framework for two years and never once needed a real answer. The interviewer wasn&#8217;t testing whether he knew Selenium syntax. She was testing whether he understood that a hardcoded sleep and a wait condition solve two completely different problems, and that only one of them survives contact with a slow CI runner. Selenium&#8217;s own documentation is blunt about the related mistake candidates make constantly: &#8220;Do not mix implicit and explicit waits. Doing so can cause unpredictable wait times&#8221; (<a href=\"https:\/\/www.selenium.dev\/documentation\/webdriver\/waits\/\" target=\"_blank\" rel=\"noopener noreferrer\">Selenium WebDriver documentation<\/a>).<\/p>\n<p>SDET (Software Development Engineer in Test) interviews sit in an odd spot between a QA interview and a backend engineering interview. You&#8217;re expected to write real code, reason about a distributed system&#8217;s failure modes, and still be fluent in test theory that a lot of pure developers never learn on the job, severity versus priority, the shape of the test pyramid, why a mock and a stub aren&#8217;t interchangeable words for the same thing. Most prep guides split neatly into &#8220;manual testing trivia&#8221; and &#8220;coding problems,&#8221; which misses the part that actually decides senior loops: whether you can explain why a test is flaky and fix the root cause instead of adding a retry and moving on.<\/p>\n<p>This page covers SDET interview questions across eight areas: core testing fundamentals and the test pyramid, Selenium and UI automation, API and service-layer testing, framework design and test patterns, CI\/CD and pipeline integration, performance and non-functional testing, coding and data structure questions specific to test tooling, and the harder architecture questions that come up once a team scales past a handful of engineers.<\/p>\n<div class=\"iq-stats not-prose\"><div class=\"iq-stat\"><span class=\"iq-stat__value\">50<\/span><span class=\"iq-stat__label\">Questions<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Test Pyramid<\/span><span class=\"iq-stat__label\">Core Model<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Selenium WebDriver<\/span><span class=\"iq-stat__label\">Common Tool<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Yes<\/span><span class=\"iq-stat__label\">Coding Round<\/span><\/div><\/div>\n<h2>SDET fundamentals: what the role actually covers<\/h2>\n<p>Every SDET loop opens somewhere near here. It&#8217;s a warm-up, but a candidate who can&#8217;t cleanly separate these ideas usually struggles later when the questions get concrete.<\/p>\n<div class=\"iq-dsec iq-dsec--easy\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"easy\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Easy questions<\/h2><span class=\"iq-dsec__n\">15<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What actually separates an SDET from a manual QA tester or a pure software engineer?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An SDET writes production-quality code, but the code&#8217;s job is building and maintaining the test infrastructure rather than shipping the feature itself: automation frameworks, custom test harnesses, CI integrations, internal tooling that other engineers and testers rely on. A manual tester executes test cases largely by hand and doesn&#8217;t necessarily write code. A pure software engineer builds the product and may write unit tests for their own code, but doesn&#8217;t usually own the automation strategy across the whole team.<\/p>\n<p>The practical difference shows up in what gets reviewed. A manual tester&#8217;s output is a test case document or a bug report. An SDET&#8217;s output is a pull request, reviewed the same way any other code change is, with the added expectation that the tests it adds are themselves reliable and fast enough that nobody starts ignoring them.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is the test pyramid, and why does the shape matter instead of just writing whatever kind of test seems easiest?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Strategy<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The test pyramid describes three layers by granularity: a wide base of fast unit tests, a narrower middle layer of service or integration tests that check how components talk to each other, and a thin top layer of full end-to-end tests that exercise the whole system through the UI. The shape matters because each layer up trades speed and isolation for realism. End-to-end tests catch things unit tests can&#8217;t, but they&#8217;re slow, they&#8217;re flakier, and a failure doesn&#8217;t tell you which component broke.<\/p>\n<p>Teams that invert the pyramid, heavy on UI tests and light on unit tests, end up with what&#8217;s sometimes called an ice-cream cone: a slow, expensive test suite that takes twenty minutes to run and still can&#8217;t tell you where a regression came from. I&#8217;d rather see a candidate say &#8220;I write ten times more unit tests than UI tests, on purpose&#8221; than list the layer names from memory.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain severity versus priority in a bug report, with a case where they disagree.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Severity measures how badly a defect breaks the system, does it crash the app, corrupt data, or just misalign a button. Priority measures how urgently it needs fixing relative to everything else in the backlog, which depends on business context, not just technical damage.<\/p>\n<p>A classic disagreement: a typo in the company&#8217;s own logo alt text on the homepage is low severity, nothing breaks, but high priority, because it&#8217;s visible to every visitor and embarrassing enough to fix before the next release. Meanwhile a crash in a rarely-used admin export feature is high severity but might sit at lower priority if only two internal users ever touch that screen.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between black box, white box, and gray box testing?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Black box testing checks behavior against requirements without looking at the code, you only see inputs and outputs. White box testing uses knowledge of the internal implementation, branches, loops, code paths, to design tests that hit specific logic. Gray box sits in between: you have partial knowledge of the internals, enough to design smarter tests, but you&#8217;re still largely validating behavior rather than tracing every line.<\/p>\n<p>An SDET typically works in white and gray box territory more than a manual tester does, since writing a unit test around a specific conditional branch requires reading the code that branch lives in.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between alpha and beta testing?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Alpha testing happens inside the company, before the product goes out the door, usually run by internal QA or dogfooding employees in an environment that mirrors production. Beta testing happens after that, with a limited set of real external users running the product in their own environment, catching the class of bugs that only shows up on hardware, networks, or usage patterns a company&#8217;s internal testers don&#8217;t have.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What makes a good bug report, beyond just &#039;it doesn&#039;t work&#039;?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A useful bug report gives someone else everything they need to reproduce the failure without asking you a follow-up question: clear reproduction steps in order, the expected result versus the actual result, environment details (browser, OS, build version), and evidence, a screenshot, a log snippet, a failing request ID.<\/p>\n<p>The detail people skip most often is the expected result. &#8220;The button doesn&#8217;t work&#8221; tells the reader nothing about what should have happened instead. Writing that line first, before describing what actually happened, forces you to confirm you actually know what correct behavior looks like.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a test case and a test scenario?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A test scenario is a high-level statement of what to test, &#8220;verify a user can check out with a saved payment method.&#8221; A test case is the detailed, step-by-step breakdown of exactly how to test that scenario, specific input values, exact click order, and the precise expected result at each step. One scenario usually expands into several concrete test cases covering different inputs and edge cases.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between smoke testing and sanity testing?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Smoke testing is a quick, broad check that the build isn&#8217;t fundamentally broken, does the app launch, does login work, does the homepage load, run right after a new build before investing time in deeper testing. Sanity testing is narrower and deeper, run after a specific bug fix or small change, to confirm that particular area of the application now behaves correctly without necessarily re-checking the whole application.<\/p>\n<p>The distinction that trips people up: smoke is broad and shallow, covering many areas briefly. Sanity is narrow and focused, covering one area in more depth after a targeted change.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why do most SDET teams push more test coverage into the API layer instead of the UI layer?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Testing<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>API tests run faster, since there&#8217;s no browser to launch and no rendering to wait on, and they&#8217;re far less brittle, since they don&#8217;t break every time a frontend team reshuffles a CSS class or adds a wrapping div. They also isolate failures better: an API test failure points directly at a broken endpoint or business rule, while a UI test failure could mean the backend broke, the frontend broke, or the test itself is timing out for an unrelated reason.<\/p>\n<p>This maps directly onto the test pyramid&#8217;s reasoning: push validation as low as it can reasonably go, and reserve UI tests for the handful of critical end-to-end flows that genuinely need to be checked through the browser.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">In JUnit or TestNG, what&#039;s the difference between @BeforeEach and @BeforeAll, and what&#039;s a real bug caused by picking the wrong one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Frameworks<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>@BeforeEach runs before every single test method, ideal for resetting state that must be clean per test. @BeforeAll runs once before any test in the class runs, meant for expensive setup that&#8217;s safe to share across tests, spinning up a database connection, say.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\n@BeforeAll\n\nstatic void setupDb() { connection = createTestDbConnection(); }\n@BeforeEach\n\nvoid resetData() { connection.execute(\u201cTRUNCATE orders\u201d); }\n<\/code><\/pre><\/div><\/p>\n<p>A real bug from picking wrong: putting data-clearing logic in @BeforeAll instead of @BeforeEach means the first test in the class runs against clean data, but every subsequent test in that class inherits whatever state the previous test left behind, silent cross-test contamination that only shows up as flaky, order-dependent failures that are miserable to track down.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between functional and non-functional testing?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Functional testing checks whether the application does what it&#8217;s supposed to do, does clicking submit actually place the order, does the search return the right results. Non-functional testing checks how well it does it, how fast, how it holds up under load, how secure it is, how usable it is for someone relying on a screen reader. A feature can pass every functional test and still be unusable in production if it&#8217;s too slow or fails under real traffic, which is exactly why both categories get their own dedicated test coverage rather than one covering for the other.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between load testing and stress testing?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Performance<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Load testing checks how the system behaves under an expected, realistic traffic level, does it stay within acceptable response times at the volume you actually expect in production. Stress testing pushes traffic well beyond expected levels specifically to find the breaking point, where does the system actually fail, and does it fail gracefully, queueing or rejecting requests cleanly, or does it fall over in a way that takes down unrelated functionality with it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Write a function that checks whether a string of brackets is balanced.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Push every opening bracket onto a stack. On a closing bracket, pop and check it matches the corresponding opening bracket, if the stack is empty or the popped value doesn&#8217;t match, the string is unbalanced. At the end, the stack must be empty for the string to be balanced.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef is_balanced(s):\n\n    pairs = {\u2018)\u2019: \u2018(\u2018, \u2018]\u2019: \u2018[\u2019, \u2018}\u2019: \u2018{\u2018}\n\n    stack = []\n\n    for char in s:\n\n        if char in \u2018([{\u2018:\n\n            stack.append(char)\n\n        elif char in \u2018)]}\u2019:\n\n            if not stack or stack.pop() != pairs[char]:\n\n                return False\n\n    return not stack\n<\/code><\/pre><\/div><\/p>\n<p>Interviewers use this to check whether a candidate reaches for a stack naturally, and whether they remember the two edge cases everyone forgets on the first pass: an empty string is balanced, and leftover unmatched opens at the end make it unbalanced even if every close matched something.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Write a function to reverse an integer, and explain the edge case that trips people up.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Convert to a string, reverse it, convert back, or do it purely with arithmetic using modulo and integer division. The edge case that trips people up: negative numbers, and integer overflow in languages with fixed-width integers, where reversing a large number can produce a result outside the valid range for that type.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef reverse_int(n):\n\n    sign = -1 if n &lt; 0 else 1\n\n    n = abs(n)\n\n    reversed_n = int(str(n)[::-1])\n\n    return sign * reversed_n\n<\/code><\/pre><\/div><\/p>\n<p>In Python this overflow edge case doesn&#8217;t actually matter, integers aren&#8217;t fixed-width. The interviewer is often testing whether you know that difference, not whether your Python code happens to work.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain how you&#039;d write a factorial function that handles the edge cases most people skip.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The recursive definition is straightforward, but the edge cases interviewers actually check are: factorial of 0 should return 1 by definition, and negative input has no valid factorial, so the function should raise an error rather than silently recursing forever or returning a nonsensical value.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef factorial(n):\n\n    if n &lt; 0:\n\n        raise ValueError(\u201cfactorial undefined for negative numbers\u201d)\n\n    if n == 0:\n\n        return 1\n\n    return n * factorial(n \u2013 1)\n<\/code><\/pre><\/div><\/p>\n<p>For very large n, a candidate who mentions recursion depth limits, and that an iterative version avoids stack overflow for large inputs, is showing they think about the failure mode, not just the happy path.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--medium\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"medium\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Medium questions<\/h2><span class=\"iq-dsec__n\">25<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between regression testing and retesting?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Retesting confirms that a specific reported bug is actually fixed, you run the exact scenario that failed before and check it now passes. Regression testing checks that the fix, or any other recent change, didn&#8217;t break something unrelated elsewhere in the system. They&#8217;re both run after a code change, but retesting is scoped to one known defect while regression testing is scoped to everything that might have been affected by the change, including features nobody touched directly.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is exploratory testing and where does it still add value in a team that automates heavily?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Exploratory testing is simultaneous learning, test design, and execution, a tester interacts with the application without a scripted test case, following their judgment about where bugs are likely to hide. Automation is good at repeating a known scenario reliably forever. It&#8217;s bad at noticing something weird that nobody thought to write a test for in the first place.<\/p>\n<p>Even on a team with strong automation coverage, exploratory sessions before a major release still catch real issues, especially around new UI flows where the &#8220;obvious&#8221; test cases haven&#8217;t been written yet because the feature is new. The two approaches aren&#8217;t competing, exploratory testing feeds the backlog of what to automate next.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the real difference between an implicit wait and an explicit wait in Selenium?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Selenium<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An implicit wait is set once, globally, on the driver, and applies to every subsequent element lookup: the driver polls for up to that duration before throwing a not-found error. An explicit wait is scoped to a single condition, you tell it exactly what to wait for, an element being clickable, visible, or containing certain text, and it polls until that condition is true or the timeout expires.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\n\/\/ implicit wait, applies to every findElement call for the session\n\ndriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));\n\/\/ explicit wait, scoped to one specific condition\n\nWebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));\n\nwait.until(ExpectedConditions.elementToBeClickable(By.id(\u201csubmit\u201d)));\n<\/code><\/pre><\/div><\/p>\n<p>Selenium&#8217;s own docs are direct about mixing the two: don&#8217;t. Combining implicit and explicit waits in the same test can produce unpredictable timeout behavior, because the driver ends up waiting on both mechanisms at once in ways that aren&#8217;t well defined (<a href=\"https:\/\/www.selenium.dev\/documentation\/webdriver\/waits\/\" target=\"_blank\" rel=\"noopener noreferrer\">Selenium documentation<\/a>). A framework with a global implicit wait and scattered explicit waits sprinkled in is a common source of the exact flaky failures interviewers ask about.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Rank the common Selenium locator strategies by how brittle they are, and explain why.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Selenium<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>ID is the most stable, IDs are usually unique and don&#8217;t shift with layout changes. CSS selectors and name attributes come next, reasonably stable unless the frontend team restructures markup. XPath, especially absolute XPath written with the full path from the document root, is the most brittle, since it breaks the moment anyone adds a wrapping div anywhere above the target element.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\n\/\/ brittle: absolute path, breaks if one ancestor div shifts\n\ndriver.findElement(By.xpath(\u201c\/html\/body\/div[2]\/div[1]\/form\/button[3]\u201d));\n\/\/ far more stable: unique id, or a relative xpath anchored on a stable attribute\n\ndriver.findElement(By.id(\u201ccheckout-submit\u201d));\n\ndriver.findElement(By.xpath(\u201c\/\/button[@data-testid=\u2019checkout-submit\u2019]\u201d));\n<\/code><\/pre><\/div><\/p>\n<p>Adding dedicated test attributes like <code>data-testid<\/code> that the frontend team agrees not to change casually is the most reliable long-term fix, and it&#8217;s a conversation worth having with frontend engineers early rather than fighting brittle selectors for months.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is the Page Object Model, and what problem does it actually solve?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Page Object Model wraps each page or component&#8217;s locators and interactions behind a class, so test code calls methods like <code>loginPage.login(user, pass)<\/code> instead of scattering raw <code>findElement<\/code> calls and locator strings across every test file. The problem it solves is maintenance: when the UI changes, a locator update happens in exactly one place, the page object, instead of in every test that happens to touch that element.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\npublic class LoginPage {\n\n  private WebDriver driver;\n\n  private By username = By.id(\u201cusername\u201d);\n\n  private By password = By.id(\u201cpassword\u201d);\n\n  private By submit = By.id(\u201clogin-submit\u201d);\n  public LoginPage(WebDriver driver) { this.driver = driver; }\n  public void login(String user, String pass) {\n\n    driver.findElement(username).sendKeys(user);\n\n    driver.findElement(password).sendKeys(pass);\n\n    driver.findElement(submit).click();\n\n  }\n\n}\n<\/code><\/pre><\/div><\/p>\n<p>Without it, a single CSS class rename can require touching dozens of test files. With it, that same change is a one-line fix in the page object.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between headless and headed browser execution, and when would you deliberately choose headed in CI?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Selenium<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Headless execution runs the browser engine without rendering an actual visible window, which is faster and lighter on CI infrastructure since there&#8217;s no display server to manage. Headed execution renders the real browser UI, useful for local debugging where you want to watch a test run and see exactly where it breaks.<\/p>\n<p>Some rendering or CSS-dependent bugs only show up in headed mode, since certain layout engines behave slightly differently without a real display context, so a suite that only ever runs headless can miss visual regressions. Most teams run headless in CI for speed and keep a headed mode available locally for debugging, rather than picking one mode exclusively.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Walk through the difference between a 401 and a 403 HTTP status code, and give an API testing scenario where confusing them would hide a real bug.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>401 Unauthorized means the request lacks valid authentication entirely, no token, an expired token, invalid credentials. 403 Forbidden means the caller is authenticated just fine, but isn&#8217;t allowed to perform this specific action or access this specific resource.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">text<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-text\">\n\nGET \/admin\/users with no auth header       -&gt; expect 401\n\nGET \/admin\/users with a valid, non-admin token -&gt; expect 403\n<\/code><\/pre><\/div><\/p>\n<p>A test that only checks &#8220;the request was rejected&#8221; without asserting the specific status code can pass even when an authorization bug flips these two, a permissions check that returns 401 instead of 403 might be leaking information about whether a resource exists at all to an unauthenticated caller, which is a real security gap that a loose assertion would never catch.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between REST and SOAP that actually changes how you&#039;d test each one?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>REST uses standard HTTP verbs and status codes, and responses are typically JSON, so testing leans heavily on asserting status codes, response schema, and headers. SOAP wraps every request and response in an XML envelope with a stricter, more formal contract defined by a WSDL file, so testing SOAP often means validating against that schema directly and paying more attention to the envelope structure, not just the payload.<\/p>\n<p>In practice REST dominates new API test suites in 2026, but plenty of SDET roles at larger, older enterprises still maintain SOAP test coverage for legacy services, and the assumption that &#8220;API testing&#8221; always means REST is worth checking with the interviewer if the job description doesn&#8217;t say.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between mocking an external dependency and hitting a real staging environment in an API test suite?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Mocking replaces the real dependency with a stand-in that returns controlled, predictable responses, fast, deterministic, and not affected by the third party&#8217;s own uptime or rate limits. Hitting a real staging environment tests actual integration behavior, including edge cases in the real service&#8217;s response format that a hand-written mock might not anticipate.<\/p>\n<p>The tradeoff: mocks give you speed and determinism but can drift from what the real service actually does if nobody updates them when the real API changes. Staging environments are more realistic but slower, flakier, and often shared across teams in ways that cause noisy, unrelated failures. Most mature suites use both: mocks for fast unit and component-level tests, a smaller set of tests against a real staging or sandbox environment to catch drift.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between data-driven testing and keyword-driven testing?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Data-driven testing runs the same test logic repeatedly against different sets of input data, usually pulled from a CSV, spreadsheet, or parameterized test annotation, so one test method covers many scenarios by varying the data. Keyword-driven testing abstracts the test steps themselves into reusable keywords, like &#8220;login,&#8221; &#8220;add to cart,&#8221; &#8220;checkout,&#8221; that non-programmers can assemble into a test case without touching the underlying code.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">java<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-java\">\n\n@ParameterizedTest\n\n@CsvSource({\u201cadmin,true\u201d, \u201cguest,false\u201d, \u201cbanned,false\u201d})\n\nvoid loginAccessTest(String role, boolean expectedAccess) {\n\n  assertEquals(expectedAccess, canAccessDashboard(role));\n\n}\n<\/code><\/pre><\/div><\/p>\n<p>Data-driven is the more common pattern on engineering-heavy SDET teams. Keyword-driven shows up more on teams where non-technical testers need to build test cases themselves, since it trades some flexibility for approachability.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the point of BDD frameworks like Cucumber, and what&#039;s the actual failure mode teams run into with them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>BDD frameworks let you write test scenarios in a structured natural-language format, Given\/When\/Then, that&#8217;s meant to be readable by product owners and business stakeholders, not just engineers, so the same document doubles as a spec and an executable test.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">gherkin<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-gherkin\">\n\nGiven a user with an empty cart\n\nWhen they add a product priced at $20\n\nThen the cart total should be $20\n<\/code><\/pre><\/div><\/p>\n<p>The failure mode teams hit constantly: the Gherkin file drifts from something a business stakeholder actually reads into something only engineers ever touch, at which point you&#8217;re maintaining an extra translation layer, the natural-language step, plus the step-definition code behind it, with no one actually reading the natural-language part anymore. When that happens the collaboration benefit BDD was supposed to give you is gone, and you&#8217;re just paying the overhead of an extra abstraction layer on top of a normal test.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a test fixture, and why does bad fixture management cause so many hard-to-diagnose test failures?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A fixture is the known, controlled state a test needs before it runs, seeded database rows, a mock server response, a specific user account. Bad fixture management, tests that share the same database rows, tests that mutate shared fixture data without cleaning up after themselves, is one of the biggest sources of order-dependent flakiness in a suite: test A passes alone, fails when run after test B, because B left the shared fixture in a state A didn&#8217;t expect.<\/p>\n<p>The fix is usually isolating fixtures per test, or per test class at minimum, and tearing state back down after each run rather than assuming the next test will get a clean slate on its own.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Where in a CI\/CD pipeline should different types of automated tests run, and why not run everything on every commit?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">CI\/CD<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Fast, cheap tests, unit tests and linting, run on every single commit or pull request, since they finish in seconds and catch the majority of regressions cheaply. Slower integration and API tests typically run on merge to a shared branch or on a scheduled interval. The slowest, most expensive tests, full end-to-end UI suites and performance tests, often run on a nightly schedule or right before a release, not on every commit.<\/p>\n<p>Running the full suite on every commit sounds thorough but in practice slows every engineer down waiting on CI, which pushes people toward skipping tests locally and just waiting for CI to tell them what broke, the opposite of the fast-feedback loop testing is supposed to give you.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you decide whether a failing test should block a deployment or just generate a warning?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">CI\/CD<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The decision usually comes down to how confident the team is in the test&#8217;s signal. A test covering a core business flow, checkout, login, payment, that has a strong track record of only failing when something is genuinely broken should block the deploy without exception. A test that&#8217;s known to be flaky, or that covers a low-risk edge case, generating a warning that a human reviews rather than an automatic hard block keeps the pipeline moving without silently ignoring real signal.<\/p>\n<p>The trap teams fall into is letting too many tests sit in &#8220;warning, not blocking&#8221; limbo. Once that list gets long enough, a genuine regression on a warning-only test gets scrolled past the same way everything else on that list does, and the whole point of having the test evaporates.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What goes in a smoke test suite that runs on every deploy, and how do you keep it from quietly growing into the full regression suite?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">CI\/CD<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A deploy-blocking smoke suite should cover only the paths that would be catastrophic if broken and that real users hit constantly, login, checkout, the core page load, nothing more. The moment it grows past ten or fifteen minutes, it&#8217;s stopped being a smoke suite and has become a slow gate that people start working around.<\/p>\n<p>Keeping it small takes active maintenance, not a one-time decision. Every time someone proposes adding &#8220;just one more&#8221; test to the blocking suite because a recent incident happened there, I&#8217;d push back and ask whether it belongs in the faster nightly regression run instead. Smoke suites bloat exactly one test at a time, each addition individually reasonable, until the whole thing is thirty minutes and nobody remembers agreeing to that.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s test data management, and why does it get harder specifically in a CI environment compared to a developer&#039;s local machine?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">CI\/CD<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Test data management is the strategy for creating, seeding, and cleaning up the data a test suite depends on, without one test&#8217;s data corrupting another&#8217;s. Locally, one developer usually has exclusive access to their own database instance, so stale or leftover data from a previous run is easy to spot and reset by hand.<\/p>\n<p>In CI, multiple pipeline runs can execute concurrently against a shared test database, or the same runner gets reused across unrelated jobs without a full reset in between, so leftover data from a failed run can silently corrupt the next run&#8217;s assumptions. Solutions that hold up: spinning up an isolated, ephemeral database per pipeline run, or namespacing all test data per run with a unique identifier so parallel runs can&#8217;t collide even on a shared instance.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a common mistake teams make when interpreting performance test results?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Performance<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Looking only at the average response time instead of the tail, the 95th or 99th percentile. An average can look perfectly healthy while a meaningful slice of real users are hitting response times several times slower than that average, because a handful of very slow outliers get smoothed out by a much larger number of fast requests when you just average them together.<\/p>\n<p>A system with a 200ms average and a 4-second p99 has a real problem that the average completely hides. Reporting percentiles, not just the mean, is the detail that separates a performance test result someone can actually act on from one that just looks reassuring on a dashboard.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s accessibility testing, and how would you fold basic checks into an existing UI automation suite without turning it into a whole separate discipline?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Non-Functional<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Accessibility testing checks whether the application works for users relying on assistive technology, screen readers, keyboard-only navigation, sufficient color contrast. A full accessibility audit is a specialized discipline, but a reasonable baseline can ride along in an existing Selenium or Playwright suite: automated axe-core scans run against each page under test catch a meaningful share of common violations, missing alt text, insufficient contrast, missing form labels, without requiring a dedicated accessibility test suite from scratch.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Swap two variables without using a temporary variable. Walk through why it works, and when you&#039;d actually never do this in real code.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>With numbers, addition and subtraction, or XOR, can swap two values without a third variable. With XOR specifically: a becomes a XOR b, then b becomes a XOR b (which is now the original a, XORed with itself against original b, isolating original a), then a becomes a XOR b again to recover original b.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\na, b = 5, 10\n\na = a ^ b\n\nb = a ^ b\n\na = a ^ b\n\n# a is now 10, b is now 5\n<\/code><\/pre><\/div><\/p>\n<p>In real production code I&#8217;d never actually do this. A temporary variable, or in Python just <code>a, b = b, a<\/code>, is instantly readable to the next person touching the code. The XOR trick is a fun interview exercise that exists because it used to matter on memory-constrained embedded systems decades ago, not because it&#8217;s good practice on a modern team.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Given an array, find all triplets that sum to a target value.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Sort the array first, then for each element, use a two-pointer sweep across the rest of the array to find pairs that sum to the target minus that element. Sorting brings this down from a brute-force triple nested loop to a much better bound, and the two-pointer technique only works cleanly once the array is sorted.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef three_sum(nums, target):\n\n    nums.sort()\n\n    result = []\n\n    for i in range(len(nums) \u2013 2):\n\n        left, right = i + 1, len(nums) \u2013 1\n\n        while left &lt; right:\n\n            total = nums[i] + nums[left] + nums[right]\n\n            if total == target:\n\n                result.append([nums[i], nums[left], nums[right]])\n\n                left += 1\n\n                right -= 1\n\n            elif total &lt; target:\n\n                left += 1\n\n            else:\n\n                right -= 1\n\n    return result\n<\/code><\/pre><\/div><\/p>\n<p>Watch for duplicate triplets in the output if the array has repeated values, a follow-up question interviewers ask almost every time once the base solution works.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Write a function that finds the first non-repeating character in a string. What&#039;s the time complexity, and can you do better than the obvious nested-loop approach?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The nested-loop approach, for each character, scan the rest of the string counting occurrences, runs in quadratic time. A single pass building a character-count map, followed by a second pass checking each character&#8217;s count in that map, gets it down to linear time at the cost of extra space for the map.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef first_unique_char(s):\n\n    counts = {}\n\n    for ch in s:\n\n        counts[ch] = counts.get(ch, 0) + 1\n\n    for ch in s:\n\n        if counts[ch] == 1:\n\n            return ch\n\n    return None\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you approach testing a feature behind a feature flag that behaves differently for different user segments?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Strategy<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Test both states of the flag explicitly rather than relying on the default state alone, since a flag flipped on in production for one segment and off for another means the code path most engineers exercise locally isn&#8217;t necessarily the one real users are hitting. I&#8217;d parameterize the test suite to run the relevant scenarios once with the flag on and once with it off, rather than writing entirely separate test files.<\/p>\n<p>I&#8217;d also specifically test the flag&#8217;s rollout boundary itself, users right at the edge of a percentage rollout, and the transition behavior when a flag gets flipped mid-session for a user already using the app, since that&#8217;s the scenario most teams forget to think about until a real user reports something broken that only happens if the flag changes state while they&#8217;re actively using the feature.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you decide how much test coverage is enough, given that 100% coverage is neither realistic nor actually the goal?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Strategy<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Coverage percentage measures which lines executed during tests, not whether the behavior at those lines was actually verified correctly, so chasing a specific number as the goal produces tests that touch a line without meaningfully asserting anything about it. I look instead at risk: which code paths handle money, security, or data integrity, and which paths get exercised by real user traffic constantly versus a rarely-used edge case.<\/p>\n<p>A reasonable target in practice is high coverage on business-critical logic and lower, more selective coverage on straightforward glue code or generated boilerplate, rather than a single blanket number applied uniformly across a whole codebase. A team that mandates 90% coverage everywhere often gets exactly 90%, achieved by testing getters and setters that never had a bug in the first place, while the genuinely risky logic sits under-tested because it was harder to test and nobody was measuring whether the coverage that existed was actually meaningful.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you design an automated test for an API endpoint that returns paginated results?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Beyond the obvious happy path, checking that page 1 returns the expected page size and a valid next-page token, I&#8217;d specifically test the edges: requesting a page number beyond the total result count, an explicit page size of zero or a negative number, and whether the total count in the response actually matches the real number of underlying records rather than an off-by-one from the pagination logic.<\/p>\n<p>I&#8217;d also check consistency across pages when the underlying data changes between requests, does inserting a new record mid-pagination shift results and cause a duplicate or a skipped record on the next page. That&#8217;s the bug pagination logic gets wrong most often in practice, and it rarely shows up in a quick manual click-through.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you design a test automation framework from scratch for a team that currently has none?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Design<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>I&#8217;d start with layering, not tool choice: a thin driver-abstraction layer so the tests themselves don&#8217;t call Selenium or an HTTP client directly, a page-object or API-client layer above that, and the actual test files calling into those, not into raw framework calls. That separation is what lets you swap tools later, moving from Selenium to Playwright, say, without rewriting every test.<\/p>\n<p>I&#8217;d push hard for the bulk of coverage to sit at the unit and API layer from day one rather than defaulting to UI tests because they&#8217;re the most obviously visible form of &#8220;testing&#8221; to non-engineers. I&#8217;d also build in reporting and CI integration early, a suite nobody looks at because the results live in a log file nobody opens gets ignored within a month, no matter how good the tests themselves are. Retrofitting reporting onto an existing framework six months in is a much bigger project than building it in from the start.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--hard\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"hard\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Hard questions<\/h2><span class=\"iq-dsec__n\">10<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is a StaleElementReferenceException and why does it happen even when the element is clearly still on the page?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Selenium<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Selenium&#8217;s WebElement object is a reference to a specific node in the DOM at the moment it was located, not a live pointer that automatically tracks the page. If the DOM gets rebuilt, a re-render, an Angular or React state update, an AJAX refresh that swaps out a container, the old node is destroyed and a visually identical new one takes its place. The WebElement you&#8217;re holding still points at the destroyed node, so any interaction with it throws StaleElementReferenceException, even though something that looks exactly the same is sitting right there on screen.<\/p>\n<p>The fix is re-locating the element right before you interact with it rather than caching a WebElement reference across steps that might trigger a re-render. It&#8217;s one of the clearest examples in Selenium of why treating a page like a static document instead of a live application causes tests to break in ways that confuse people new to browser automation.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is a flaky test, and how do you tell a genuinely flaky test apart from a test that&#039;s correctly catching a real intermittent bug?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Reliability<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A flaky test passes and fails on the exact same code with no relevant change in between, which usually points to the test itself, a race condition, shared test data, order dependency, rather than the product. A test correctly catching a real bug fails consistently under the same conditions that trigger the actual defect, and a fix to the product, not the test, makes it pass reliably again.<\/p>\n<p>The way to tell them apart is to run the failing test in isolation, repeatedly, under the same conditions. If it fails 100% of the time when run alone but only sometimes in the full suite, that&#8217;s usually shared state or ordering, not a real product bug. If it fails inconsistently even fully isolated, look for a genuine race condition in the product itself, an async operation the UI doesn&#8217;t properly wait on before rendering. Quarantining a test to &#8220;known flaky, ignore&#8221; without doing this investigation first is how real bugs quietly stop getting caught.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s contract testing, and why would a team add it on top of existing integration tests?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Testing<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Contract testing verifies that a consumer service and a provider service agree on the shape of the API between them, request format, response schema, required fields, without spinning up both services together in a full integration environment. A consumer-driven contract test captures what the consumer actually expects from the provider, then that same contract gets replayed against the real provider in its own pipeline to confirm it still honors it.<\/p>\n<p>The reason to add it on top of integration tests: full integration tests across service boundaries are slow, require standing up dependent services, and often live in a separate repo from the team that owns the change that just broke them, so a provider team can ship a breaking change and only find out when a downstream team&#8217;s tests fail hours or days later. Contract tests catch the same class of breakage fast, inside each team&#8217;s own pipeline, without needing the other service running at all.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does idempotency mean for an API endpoint, and how would you write a test that actually verifies it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">API Testing<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An idempotent endpoint produces the same result no matter how many times the same request is sent, calling it once or five times leaves the system in the same state. GET, PUT, and DELETE are supposed to be idempotent by the HTTP spec; POST typically isn&#8217;t, since a repeated POST usually creates a new resource each time.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">text<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-text\">\n\nPUT \/orders\/123 { status: \u201cshipped\u201d }\n\n\/\/ call it once, call it three times in a row\n\n\/\/ state after 1 call should equal state after 3 calls\n<\/code><\/pre><\/div><\/p>\n<p>A real test sends the identical request multiple times and asserts the final state and side effects are the same regardless of call count, not just that each individual response returned 200. This matters most for payment and order-status endpoints, where a client retry after a network timeout must not double-charge or double-ship because the endpoint wasn&#8217;t actually idempotent under the hood.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between running tests in parallel across multiple machines versus multiple threads on one machine, and what breaks first when you parallelize a suite that wasn&#039;t designed for it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">CI\/CD<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Multi-machine parallelization splits the whole test suite across separate CI runners, each with its own isolated environment, so tests can&#8217;t interfere with each other through shared local state. Multi-threaded parallelization on one machine runs multiple tests concurrently within the same process or environment, faster to set up but far more exposed to shared state, a shared database connection, a shared temp file, a static variable that isn&#8217;t thread-safe.<\/p>\n<p>The first thing that breaks in a suite not designed for parallel execution is shared test data: two tests that both create a user with the same hardcoded email address will race and one will fail with a duplicate-key error that has nothing to do with the actual feature being tested. The fix is generating unique test data per test run, a random suffix on emails and IDs, rather than hardcoding fixture values that assume exclusive access.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you test for a memory leak in a long-running service as part of an SDET role, without access to the production monitoring dashboards?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Performance<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Run a soak test, sustained, moderate load over an extended period, hours rather than minutes, while sampling the process&#8217;s memory usage at regular intervals. A service without a leak should show memory usage rise initially and then plateau as garbage collection or normal cleanup keeps pace. A service with a leak shows memory climbing steadily with no plateau, even under constant, unchanging load.<\/p>\n<p>Without production dashboards, tools like a language runtime&#8217;s built-in heap profiler, or simply capturing process memory stats on a timer during the soak test and graphing them afterward, are usually enough to spot the pattern. The key detail interviewers want to hear is &#8220;sustained load over time,&#8221; not a short burst, since a short load test frequently doesn&#8217;t run long enough for a slow leak to become visible against normal memory fluctuation.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you detect a duplicate in an array using the least extra memory, and what&#039;s the tradeoff versus using a hash set?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A hash set gives linear time at the cost of linear extra space, insert each element and check membership before inserting, the first element already present is a duplicate. To use less memory, if the array&#8217;s values are constrained to a known range (say, integers from 1 to n for an array of length n), you can use the array itself as a marker, negating the value at the index corresponding to each number seen, then any index whose value is already negative when you go to negate it again reveals a duplicate.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef find_duplicate(nums):\n\n    for num in nums:\n\n        idx = abs(num) \u2013 1\n\n        if nums[idx] &lt; 0:\n\n            return abs(num)\n\n        nums[idx] = -nums[idx]\n\n    return -1\n<\/code><\/pre><\/div><\/p>\n<p>The tradeoff is real: the in-place approach mutates the input array, which is unacceptable if the caller needs the original data intact afterward, and it only works under that specific value-range constraint. A hash set works on any input with no constraint, at the cost of extra space. I&#8217;d default to the hash set unless memory was a demonstrated, measured constraint.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A critical end-to-end test suite takes 45 minutes to run and blocks every deploy. How would you actually fix that, beyond &#039;just add more parallel runners&#039;?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Strategy<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>More runners helps but doesn&#8217;t fix the underlying problem if the suite is testing the wrong things at the wrong layer. I&#8217;d start by auditing what each end-to-end test is actually verifying, and for any test whose assertion could be made just as confidently at the API or unit layer, without needing a real browser, I&#8217;d push it down the pyramid instead of just paying to run it faster. A lot of &#8220;end-to-end&#8221; suites accumulate tests that never needed a browser in the first place, they were just easiest to write that way at the time.<\/p>\n<p>After that pruning, I&#8217;d parallelize what&#8217;s left across runners, and separate the suite into a small, fast smoke set that blocks every deploy versus a larger nightly regression set that runs on a schedule and doesn&#8217;t block anyone in the critical path. The goal is a deploy-blocking suite measured in minutes, not tens of minutes, with the comprehensive coverage still running, just not gating every single release.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a mock, a stub, and a fake, and why does mixing these terms up in a design discussion actually cause real confusion?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Test Design<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A stub returns a fixed, predetermined response when called, it doesn&#8217;t verify anything about how it was called. A mock does verify interactions, it can assert that it was called a specific number of times, with specific arguments, and fail the test if that expectation isn&#8217;t met. A fake is a working, simplified implementation of the real thing, an in-memory database standing in for a real one, that behaves correctly but skips the production-grade complexity (<a href=\"https:\/\/martinfowler.com\/articles\/practical-test-pyramid.html\" target=\"_blank\" rel=\"noopener noreferrer\">Martin Fowler, Practical Test Pyramid<\/a>).<\/p>\n<p>The confusion matters in practice because these three have different failure modes. Overusing mocks that assert on exact call arguments makes tests brittle to harmless refactors, since a test can fail even when behavior is unchanged just because an internal call pattern shifted. A team that calls everything &#8220;a mock&#8221; regardless of which of the three it actually is tends to reach for the most rigid option by default, and ends up with a test suite that breaks constantly on refactors that didn&#8217;t change any actual behavior.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Your Selenium suite passes locally but fails intermittently in CI. Walk through how you&#039;d actually debug it.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Selenium<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>First I&#8217;d check whether it&#8217;s the same test failing every time or a different one each run, that alone tells you whether you&#8217;re looking at a specific timing bug or environmental flakiness across the board. Then I&#8217;d look at what&#8217;s different about CI versus local: usually slower or shared hardware, headless mode instead of a real browser window, different screen resolution affecting element visibility, or parallel test runs competing for the same test data.<\/p>\n<p>The most common root cause in practice is a race condition between the test and an async UI update, a modal that takes longer to render under CI load than it does on a fast local machine, so a click fires before the element is actually interactable. I&#8217;d add explicit waits tied to the actual condition (element clickable, network request finished) rather than bumping a global timeout number, since a longer timeout just delays the failure without fixing the race. Screenshots and browser console logs captured on failure, uploaded as CI artifacts, turn a &#8220;it&#8217;s flaky&#8221; report into an actual root cause most of the time.<\/p>\n<p><\/div><\/div><\/div>\n<h2>How to prepare for an SDET interview<\/h2>\n<p>Skip re-reading a list of test-pyramid definitions the night before. Build one small thing that forces you to make real tradeoffs: a Page Object Model wrapper around a login flow with an explicit wait instead of a sleep, an API test suite for a public endpoint that checks status codes and idempotency, a parallel-safe test data setup that generates unique fixtures per run instead of hardcoding values. Watching your own suite go from flaky to reliable teaches more about waits, fixtures, and race conditions than reading about them ever will.<\/p>\n<p>Across mock interviews run through LastRoundAI, the flaky-test debugging question trips up more candidates than the coding round does, mostly because candidates prep coding problems on purpose but rarely rehearse explaining a debugging process out loud under time pressure. Practicing the explanation, not just knowing the answer in your head, is the part most people skip.<\/p>\n<p>One more thing worth knowing: SDET roles increasingly expect comfort across the whole stack, unit tests in the application&#8217;s own language, API tests in a separate service-layer suite, and enough CI\/CD literacy to own pipeline integration rather than handing that off to a separate DevOps team. A candidate who can only speak to UI automation is competing for a narrower slice of the roles actually open in 2026 than one who can speak to all three layers.<\/p>\n<h2>Get the reps in before the real thing<\/h2>\n<p>Explaining why a test is flaky on paper is not the same as defending your debugging process out loud when an interviewer keeps pushing &#8220;but what if it&#8217;s not that.&#8221; <a href=\"\/products\/mock-interviews\">LastRoundAI&#8217;s mock interview mode<\/a> runs live technical rounds with real-time follow-up questions in your browser, and the free plan includes 15 credits a month that reset monthly rather than piling up unused. Starter is $19\/mo if a handful of sessions isn&#8217;t enough runway.<\/p>\n<p>Once your answers hold up under a follow-up, the slower part of the job hunt is usually just getting in front of enough SDET and QA automation roles that actually test the skills this page covers instead of just asking for years of Selenium on a resume. <a href=\"\/products\/auto-apply\">Auto-Apply<\/a> queues tailored applications for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.<\/p>\n<p>Questions about either product go to contact@lastroundai.com. That&#8217;s the only inbox we check.<\/p>\n<div class=\"iq-sources not-prose\"><div class=\"iq-sources__title\">Sources &amp; further reading<\/div><ul class=\"iq-sources__list\"><li><a href=\"https:\/\/www.selenium.dev\/documentation\/webdriver\/waits\/\" target=\"_blank\" rel=\"nofollow noopener\">Selenium WebDriver Documentation, Waits<\/a><\/li><li><a href=\"https:\/\/martinfowler.com\/articles\/practical-test-pyramid.html\" target=\"_blank\" rel=\"nofollow noopener\">Martin Fowler, Practical Test Pyramid<\/a><\/li><li><a href=\"https:\/\/www.interviewbit.com\/sdet-interview-questions\/\" target=\"_blank\" rel=\"nofollow noopener\">InterviewBit, SDET Interview Questions<\/a><\/li><\/ul><\/div>\n","protected":false},"excerpt":{"rendered":"<p>A candidate at a mid-size logistics company got asked last year to explain why a Selenium test kept failing intermittently on a button that clicked fine every single time a human tried it by hand. He&#8217;d used Thread.sleep(3000) everywhere in his framework for two years and never once needed a real answer. The interviewer wasn&#8217;t&#8230;<\/p>\n","protected":false},"author":6,"featured_media":2056,"comment_status":"open","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"tags":[],"class_list":["post-2031","iq","type-iq","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>SDET Interview Questions (2026): Q&amp;A | LastRoundAI<\/title>\n<meta name=\"description\" content=\"50 real SDET interview questions for 2026: Selenium waits, flaky tests, API testing, test pyramid, CI\/CD, and coding rounds, with senior-level answers.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lastroundai.com\/interview-questions\/sdet\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"SDET Interview Questions (2026): Q&amp;A | LastRoundAI\" \/>\n<meta property=\"og:description\" content=\"50 real SDET interview questions for 2026: Selenium waits, flaky tests, API testing, test pyramid, CI\/CD, and coding rounds, with senior-level answers.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lastroundai.com\/interview-questions\/sdet\" \/>\n<meta property=\"og:site_name\" content=\"LastRound AI\" \/>\n<meta property=\"og:image\" content=\"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-sdet-og.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"36 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/sdet\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/sdet\",\"name\":\"SDET Interview Questions (2026): Q&A | LastRoundAI\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/sdet#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/sdet#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-sdet-og.png\",\"datePublished\":\"2026-07-21T18:30:00+00:00\",\"description\":\"50 real SDET interview questions for 2026: Selenium waits, flaky tests, API testing, test pyramid, CI\\\/CD, and coding rounds, with senior-level answers.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/sdet#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/sdet\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/sdet#primaryimage\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-sdet-og.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-sdet-og.png\",\"width\":1200,\"height\":630,\"caption\":\"SDET interview questions \u2014 LastRoundAI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/sdet#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interview Questions\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"SDET Interview Questions (2026): Most Asked, With Answers\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"name\":\"LastRound AI\",\"description\":\"Interview Assistant prep, tech careers and AI tools\",\"publisher\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\",\"name\":\"LastRound AI\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"width\":400,\"height\":400,\"caption\":\"LastRound AI\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"SDET Interview Questions (2026): Q&A | LastRoundAI","description":"50 real SDET interview questions for 2026: Selenium waits, flaky tests, API testing, test pyramid, CI\/CD, and coding rounds, with senior-level answers.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lastroundai.com\/interview-questions\/sdet","og_locale":"en_US","og_type":"article","og_title":"SDET Interview Questions (2026): Q&A | LastRoundAI","og_description":"50 real SDET interview questions for 2026: Selenium waits, flaky tests, API testing, test pyramid, CI\/CD, and coding rounds, with senior-level answers.","og_url":"https:\/\/lastroundai.com\/interview-questions\/sdet","og_site_name":"LastRound AI","og_image":[{"width":1200,"height":630,"url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-sdet-og.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"36 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lastroundai.com\/interview-questions\/sdet","url":"https:\/\/lastroundai.com\/interview-questions\/sdet","name":"SDET Interview Questions (2026): Q&A | LastRoundAI","isPartOf":{"@id":"https:\/\/lastroundai.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/lastroundai.com\/interview-questions\/sdet#primaryimage"},"image":{"@id":"https:\/\/lastroundai.com\/interview-questions\/sdet#primaryimage"},"thumbnailUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-sdet-og.png","datePublished":"2026-07-21T18:30:00+00:00","description":"50 real SDET interview questions for 2026: Selenium waits, flaky tests, API testing, test pyramid, CI\/CD, and coding rounds, with senior-level answers.","breadcrumb":{"@id":"https:\/\/lastroundai.com\/interview-questions\/sdet#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lastroundai.com\/interview-questions\/sdet"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/interview-questions\/sdet#primaryimage","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-sdet-og.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-sdet-og.png","width":1200,"height":630,"caption":"SDET interview questions \u2014 LastRoundAI"},{"@type":"BreadcrumbList","@id":"https:\/\/lastroundai.com\/interview-questions\/sdet#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/lastroundai.com\/blog"},{"@type":"ListItem","position":2,"name":"Interview Questions","item":"https:\/\/lastroundai.com\/interview-questions"},{"@type":"ListItem","position":3,"name":"SDET Interview Questions (2026): Most Asked, With Answers"}]},{"@type":"WebSite","@id":"https:\/\/lastroundai.com\/blog\/#website","url":"https:\/\/lastroundai.com\/blog\/","name":"LastRound AI","description":"Interview Assistant prep, tech careers and AI tools","publisher":{"@id":"https:\/\/lastroundai.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lastroundai.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lastroundai.com\/blog\/#organization","name":"LastRound AI","url":"https:\/\/lastroundai.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","width":400,"height":400,"caption":"LastRound AI"},"image":{"@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/2031","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq"}],"about":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/types\/iq"}],"author":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/comments?post=2031"}],"version-history":[{"count":1,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/2031\/revisions"}],"predecessor-version":[{"id":2066,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/2031\/revisions\/2066"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media\/2056"}],"wp:attachment":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media?parent=2031"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/tags?post=2031"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}