Back to guides

GUIDE / automation

Implicit vs Explicit Waits in Selenium

Compare implicit vs explicit waits in Selenium with clear examples, timing rules, common pitfalls, and reliable patterns that reduce flaky tests.

By The Testing AcademyPublished July 9, 2026Updated July 9, 202615 min read

If you are comparing implicit vs explicit waits in Selenium style strategies, you are really choosing how your suite decides the app is ready. Waits sit between fast machines and slow user interfaces. Too little waiting creates flakes. Too much waiting creates sluggish pipelines. The wrong wait type creates both.

This guide explains implicit waits, explicit waits, fluent waits, expected conditions, anti-patterns like hard sleep, and a practical policy you can adopt in real frameworks. You will get Java and Python examples, comparison tables, and debugging tactics for timeout failures.

Quick Answer

Wait typeScopeWaits forBeginner recommendation
ImplicitGlobal element lookupElement presence in DOM via findKeep at 0 in modern suites
Explicit (WebDriverWait)Specific call siteA named conditionPrimary strategy
FluentWaitSpecific, configurableCustom condition and pollingAdvanced explicit variant
sleep / Thread.sleepFixed timeClock onlyAvoid except rare temporary debug

Practical default for many teams: implicit wait off, explicit waits everywhere readiness matters.

Why Waits Exist

Browsers and apps are asynchronous. After a click, any of these may happen later:

  • Network response returns
  • DOM nodes render
  • Animations complete
  • Buttons become enabled
  • Navigations finish
  • Toasts appear and disappear
  • Client side validation runs

Selenium commands are synchronous from your test's point of view. If you click before a button is ready, or read text before a node exists, the test fails even when the product would work for a human who waited half a second.

Waits bridge that gap with polling and timeouts instead of hope.

What Is an Implicit Wait?

An implicit wait tells the WebDriver client how long to keep trying when it cannot immediately find an element.

Java

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

Python

driver.implicitly_wait(10)

After this setting, a find_element call may poll for up to 10 seconds before throwing NoSuchElementException.

Characteristics

  • Global for the driver session until changed
  • Applies to element lookup behavior
  • Simple to turn on
  • Easy to misuse as a fake stability strategy

What implicit wait does not truly solve

Implicit wait does not understand:

  • "Clickable" vs only present
  • "Text equals dashboard"
  • "URL contains /checkout"
  • "Spinner is gone"
  • "Option list finished loading"

An element can be present in the DOM and still covered, disabled, or incomplete.

What Is an Explicit Wait?

An explicit wait waits for a specific condition at a specific point in the test, with a timeout you choose for that wait.

Java

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement button = wait.until(
    ExpectedConditions.elementToBeClickable(By.id("submit"))
);
button.click();

Python

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
button = wait.until(EC.element_to_be_clickable((By.ID, "submit")))
button.click()

Characteristics

  • Local and intention revealing
  • Tied to a real readiness condition
  • Easier to tune per step
  • Better failure messages when conditions are well chosen

Explicit waits are the core professional skill for Selenium stability.

Implicit vs Explicit Waits in Selenium: Full Comparison

DimensionImplicit waitExplicit wait
ScopeDriver globalSpecific statement
Intention"Keep looking for elements""Wait until this condition is true"
Condition richnessLowHigh
DebuggabilityOpaqueClearer if condition is named well
Risk when mixedHighManaged when implicit is zero
Best useRare compatibility casesDefault for UI readiness
Typical failureNoSuchElementException after global timeoutTimeoutException for unmet condition

The important product lesson: presence is not readiness. Explicit waits express readiness.

Expected Conditions You Will Use Constantly

Common conditions:

  • presenceOfElementLocated
  • visibilityOfElementLocated
  • elementToBeClickable
  • invisibilityOfElementLocated
  • textToBePresentInElementLocated
  • urlContains
  • titleContains
  • frameToBeAvailableAndSwitchToIt
  • numberOfElementsToBeMoreThan
  • alertIsPresent

Choosing the right condition

SituationCondition style
Element enters DOM but may be hiddenpresence, then maybe visibility
User must see itvisibility
User must click itclickable
Loading spinner should leaveinvisibility
Navigation finished to target routeurlContains / urlToBe
Table finished rendering rowsnumber of elements or specific cell text

Beginners often stop at presence. That is why they still see intercepted clicks.

FluentWait: Explicit Wait With More Control

FluentWait lets you configure polling and ignored exceptions.

Java sketch

Wait<WebDriver> wait = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(15))
    .pollingEvery(Duration.ofMillis(300))
    .ignoring(NoSuchElementException.class)
    .ignoring(StaleElementReferenceException.class);

WebElement el = wait.until(d -> d.findElement(By.id("result")));

Use FluentWait when:

  • Default polling is not ideal
  • You need custom conditions
  • You want to ignore specific transient exceptions during polling

For most tests, WebDriverWait plus expected conditions is enough.

Why Mixing Implicit and Explicit Waits Hurts

This is one of the most important practical warnings in Selenium.

If implicit wait is 10 seconds and explicit wait is 10 seconds, the internal element lookups performed while evaluating conditions may interact with the implicit timeout. The result can be:

  • Hard to predict total wait time
  • Slower failures
  • Confusing timeout behavior
  • Suites that "sometimes wait forever" from a human perspective

Many experienced teams adopt this policy:

implicitlyWait = 0
use explicit waits for all readiness checks

That policy makes timing model simple.

Hard Sleeps: Why They Feel Good and Fail Later

Thread.sleep(3000);
time.sleep(3)

Hard sleeps are popular because they are easy. They are bad defaults because:

  1. They wait even when the app was ready in 200 ms
  2. They still fail when the app needs 3.1 seconds
  3. They hide race conditions instead of modeling them
  4. They make suite runtime balloon

Temporary debug sleeps are understandable. Permanent suite sleeps are technical debt.

A Practical Wait Policy for Frameworks

Adopt a written policy:

  1. Set implicit wait to zero
  2. Create a shared wait helper with a default timeout
  3. Use short timeouts for quick UI transitions when safe
  4. Use longer timeouts for known slow reports or imports
  5. Never put sleeps in page objects without a linked ticket
  6. Prefer waiting for conditions that match user outcomes
  7. Log or attach screenshots on timeout

Example helper:

class Waits:
    def __init__(self, driver, timeout=10):
        self.driver = driver
        self.timeout = timeout

    def clickable(self, locator):
        return WebDriverWait(self.driver, self.timeout).until(
            EC.element_to_be_clickable(locator)
        )

    def visible(self, locator):
        return WebDriverWait(self.driver, self.timeout).until(
            EC.visibility_of_element_located(locator)
        )

    def gone(self, locator):
        return WebDriverWait(self.driver, self.timeout).until(
            EC.invisibility_of_element_located(locator)
        )

Page objects then call intention revealing methods instead of raw wait soup. This fits cleanly with the Page Object Model.

Page Object Examples With Explicit Waits

class LoginPage:
    EMAIL = (By.ID, "email")
    PASSWORD = (By.ID, "password")
    SUBMIT = (By.CSS_SELECTOR, "button[type=submit]")
    ALERT = (By.CSS_SELECTOR, "[role=alert]")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def login(self, email: str, password: str):
        self.wait.until(EC.visibility_of_element_located(self.EMAIL)).send_keys(email)
        self.driver.find_element(*self.PASSWORD).send_keys(password)
        self.wait.until(EC.element_to_be_clickable(self.SUBMIT)).click()

    def error_text(self) -> str:
        return self.wait.until(EC.visibility_of_element_located(self.ALERT)).text

Notice the waits express product readiness, not random delays.

Other Timeout Settings People Confuse With Waits

Selenium also has:

  • Page load timeout
  • Script timeout
driver.set_page_load_timeout(30)
driver.set_script_timeout(20)

These are not replacements for explicit waits on dynamic widgets after a page has already loaded. They cover navigation and async script execution limits.

Explicit Wait Patterns for Common UI Problems

Wait for spinner to disappear, then assert content

wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".spinner")))
wait.until(EC.visibility_of_element_located((By.ID, "results")))

Wait for options in a dynamic dropdown

wait.until(EC.element_to_be_clickable((By.ID, "city-option-bangalore"))).click()

For select boxes and custom lists, pair the wait with the interaction patterns in how to handle dropdowns in Selenium.

Wait for URL after login

wait.until(EC.url_contains("/dashboard"))

Wait for text change

wait.until(EC.text_to_be_present_in_element((By.ID, "cart-count"), "3"))

Wait for stale re-render to finish

Re-find after condition instead of reusing old element references. Stale element issues often appear when lists refresh. Condition based re-query is safer.

How Timeouts Should Be Chosen

There is no universal perfect number.

Guidelines:

  • Local dev UI: 5 to 10 seconds often enough
  • Heavier staging with cold starts: 15 to 30 seconds for known slow paths
  • Smoke critical path: fail reasonably fast so CI signals quickly
  • Rare long reports: dedicated longer wait at that step only

A global 60 second explicit wait everywhere hides performance regressions and slows failure feedback.

Debugging TimeoutException

When an explicit wait times out:

  1. Screenshot the final UI
  2. Confirm the locator still matches in devtools
  3. Check whether the element is in an iframe
  4. Check whether the element is present but not visible
  5. Check whether another overlay intercepts
  6. Check environment data setup
  7. Check whether the condition was too weak or too strong

Timeout is a symptom. The root cause may be wrong locator, wrong environment, slower app, or wrong condition.

For broader flake taxonomy, use how to fix flaky tests.

Implicit Wait Myths

Myth 1: "A 10 second implicit wait makes tests stable"

It can reduce some NoSuchElement noise and still leave click interception flakes untouched.

Myth 2: "Explicit waits are only for advanced users"

Explicit waits are beginner essentials. Learn them early.

Myth 3: "One wait style must be used exclusively forever"

The practical recommendation is explicit first. Implicit zero is a policy for clarity, not a moral law from a standards body.

Myth 4: "If Selenium finds the element, it is safe to click"

Presence is not actionability.

Comparison With Playwright Auto-Wait

If you also use Playwright, you will notice fewer manual waits because actions auto-wait for actionability. That does not mean waits disappear as a concept. It means the framework encodes many explicit conditions for you.

Selenium makes those conditions your responsibility. That can be educational and verbose at the same time. For tool choice context, see Selenium vs Playwright vs Cypress.

Framework Level Design

In larger frameworks:

  • Centralize wait helpers
  • Standardize default timeouts by environment
  • Put waits inside page interactions, not raw tests, when possible
  • Keep tests readable: cart.addItem(item) should wait internally
  • Emit diagnostics on timeout

This is part of building a maintainable system, not only a script pile. The broader structure advice in build a test automation framework applies.

Worked Example: Flaky Submit Button

Bad approach

driver.find_element(By.ID, "email").send_keys("qa.user@example.com")
driver.find_element(By.ID, "password").send_keys("ValidPass#2026")
time.sleep(3)
driver.find_element(By.ID, "submit").click()

Better approach

wait = WebDriverWait(driver, 10)
wait.until(EC.visibility_of_element_located((By.ID, "email"))).send_keys("qa.user@example.com")
driver.find_element(By.ID, "password").send_keys("ValidPass#2026")
wait.until(EC.element_to_be_clickable((By.ID, "submit"))).click()
wait.until(EC.url_contains("/dashboard"))

The better version states readiness at each critical boundary.

Common Mistakes

Mistake 1: Giant implicit wait plus scattered sleeps

This creates the slowest, least diagnosable suite style.

Mistake 2: Waiting for presence then clicking immediately

Use clickable when clicking.

Mistake 3: One huge timeout for every tiny UI toggle

Failures become slow and performance bugs hide.

Mistake 4: Catching timeout and ignoring it

Swallowed timeouts turn real product issues into silent continues.

Mistake 5: Putting every wait in the test class instead of page objects

Duplication multiplies and conditions drift.

Mistake 6: Wrong condition for spinners

Waiting for presence of results while a spinner still blocks interaction yields intercepted clicks.

Mistake 7: Not resetting mental model when switching tools

Playwright auto-wait habits do not transfer unchanged into Selenium.

Mistake 8: Treating waits as the only flake fix

Bad locators, shared test data, and order dependent tests also create flakes. Waits cannot repair broken design alone.

Migration Checklist: From Sleepy Suite to Explicit Suite

  1. Inventory all sleep calls
  2. Set implicit wait to 0 in driver setup
  3. Replace sleeps with named expected conditions
  4. Move repeated wait logic into helpers or page objects
  5. Add failure screenshots on timeout
  6. Re-run historically flaky tests 20 to 50 times
  7. Tune specific slow paths with local longer timeouts
  8. Document the wait policy in the repo README

Practice Exercise

Take a login flow and intentionally write three versions:

  1. No waits
  2. Implicit wait only
  3. Explicit waits only with implicit zero

Run each under throttled network if you can. Observe which fails, which is slow, and which is clear in error messages. That experiment teaches implicit vs explicit waits in Selenium behavior better than memorizing definitions.

Then apply the explicit style to a real practice flow and validate your judgment in QABattle by first noting which UI states are asynchronous when you test manually.

Final Recommendation

For modern Selenium suites:

  • Prefer explicit waits with expected conditions
  • Keep implicit waits at zero unless you have a deliberate compatibility reason
  • Avoid hard sleeps in committed code
  • Wait for user meaningful readiness: visible, clickable, URL, text, spinner gone
  • Centralize wait helpers and diagnostics

If you remember one line about implicit vs explicit waits in Selenium discussions, remember this: implicit waits only stretch element lookup time, while explicit waits express the real condition that makes the next action valid. Express the condition, and your suite becomes both faster and more honest.

Expected Condition Recipes With Intent

Recipe: safe click

el = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.ID, "save"))
)
el.click()

Use when the next action is a click.

Recipe: readable text assertion readiness

WebDriverWait(driver, 10).until(
    EC.text_to_be_present_in_element((By.ID, "status"), "Submitted")
)

Use when the product signals completion through text.

Recipe: navigation boundary

old = driver.current_url
button.click()
WebDriverWait(driver, 10).until(EC.url_changes(old))

Use when a click should change location.

Recipe: list finished loading

WebDriverWait(driver, 15).until(
    lambda d: len(d.find_elements(By.CSS_SELECTOR, "table tbody tr")) >= 1
)

Custom conditions are valid when expected conditions do not express your need directly.

Custom Expected Conditions

In Java, implement Function<WebDriver, Boolean> or use lambdas with newer bindings. In Python, any callable that receives the driver and returns a truthy value can be used with WebDriverWait.until.

Example: wait until a button label changes from Saving to Save.

def label_is_save(driver):
    text = driver.find_element(By.ID, "save").text.strip()
    return text == "Save"

WebDriverWait(driver, 10).until(label_is_save)

Custom conditions keep tests honest about product semantics.

Measuring Whether Your Wait Policy Works

Track these signals for a month:

  • flake rate by test
  • average test duration
  • timeout failure rate
  • number of sleeps remaining in repo
  • mean time to diagnose a timeout

If duration falls but flake rate rises, waits became too aggressive. If flake rate falls but suite time explodes, timeouts may be too large or sleeps returned.

A healthy implicit vs explicit waits in Selenium policy improves both stability and signal quality.

Team Conventions to Write Down

Put these lines in CONTRIBUTING or framework docs:

1. implicit wait is always 0
2. no Thread.sleep in committed code without a ticket link
3. page interactions wait for readiness internally
4. default explicit timeout is 10s locally, override per slow feature
5. every new timeout exception should include a screenshot

Conventions beat tribal knowledge.

Handling Animations and Delayed Enablement

Modern UIs disable buttons while saving, then re-enable them. A presence only wait can click too early or too late.

Better sequence:

  1. Click save
  2. Wait for saving indicator or disabled state if it appears
  3. Wait for success state or re-enabled control
  4. Assert final domain outcome

Example:

save = wait.until(EC.element_to_be_clickable((By.ID, "save")))
save.click()
wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".saving-indicator")))
wait.until(EC.text_to_be_present_in_element((By.ID, "toast"), "Saved"))

Model the state machine, not only the first element you see.

Drivers, Grids, and Wait Behavior

When tests run on Selenium Grid or cloud providers, latency increases. Explicit waits still work, but:

  • avoid tiny 1 second timeouts for remote runs
  • keep conditions precise so you do not burn full timeouts repeatedly
  • prefer stable environments over raising every timeout to 60 seconds

If only remote runs fail, investigate network and environment first before blaming the wait API.

Teaching Juniors the Mental Model

A useful teaching sentence:

Do not wait because time passes. Wait because a condition becomes true.

Exercises:

  1. Break a test with a sleep that is too short
  2. Replace with a wrong condition and observe
  3. Replace with the right condition and observe
  4. Compare total runtime across 20 iterations

This creates intuition faster than slide decks.

Full Example: From Flaky to Stable

Flaky version

driver.find_element(By.ID, "add-to-cart").click()
time.sleep(2)
driver.find_element(By.ID, "cart-link").click()
assert "1" in driver.find_element(By.ID, "cart-count").text

Stable version

wait = WebDriverWait(driver, 10)
wait.until(EC.element_to_be_clickable((By.ID, "add-to-cart"))).click()
wait.until(EC.text_to_be_present_in_element((By.ID, "cart-count"), "1"))
wait.until(EC.element_to_be_clickable((By.ID, "cart-link"))).click()
wait.until(EC.url_contains("/cart"))
assert wait.until(EC.visibility_of_element_located((By.ID, "cart-count"))).text.strip() == "1"

The stable version states each readiness boundary: clickable add button, count updated, cart link ready, navigation done, final count visible.

Code Review Checklist for Waits

Reviewers should ask:

  • Is implicit wait still zero?
  • Are new sleeps justified and temporary?
  • Does each wait condition match the user outcome?
  • Are timeouts unusually large without reason?
  • Are waits hidden in page objects for repeated interactions?
  • Do timeout failures capture diagnostics?

Make wait quality part of normal review, not an emergency response after CI goes red for a week.

When the App Is Simply Too Slow

Sometimes tests time out because the product performance is poor. Do not hide that with 90 second waits forever. File a performance defect, keep a temporary explicit timeout with a comment and ticket link, and re-evaluate after the fix. Test waits should not become the only performance monitoring system, but they can surface pain early.

Closing Policy You Can Paste Into a Repo

Wait policy:
- implicit wait = 0
- prefer explicit WebDriverWait conditions
- no committed hard sleeps without a ticket
- default timeout 10s, override only for known slow features
- on timeout: screenshot + current URL + locator details in logs

If your team follows that policy consistently, debates about implicit vs explicit waits in Selenium become short, and suites fail for product reasons more often than for timing confusion.

FAQ

Questions testers ask

What is the difference between implicit and explicit waits in Selenium?

An implicit wait sets a global polling timeout for finding elements. An explicit wait waits for a specific condition on a specific target, such as clickable or visible, with its own timeout. Explicit waits are usually clearer and safer for complex modern apps.

Should I use implicit or explicit waits in Selenium?

Prefer explicit waits for real user-facing conditions. Many teams set implicit wait to zero and use WebDriverWait with expected conditions. Mixing a large implicit wait with explicit waits can make timing behavior harder to reason about.

What is WebDriverWait in Selenium?

WebDriverWait is the standard explicit wait API. You provide a timeout and a condition, and Selenium polls until the condition is true or the timeout expires. Typical conditions include visibility, clickability, URL changes, and presence of text.

Why is Thread.sleep a bad wait strategy?

Hard sleeps always wait the fixed time even when the app is ready earlier, which slows suites. They also fail when the app is slower than the sleep. Condition based waits are faster when the app is quick and safer when the app is slow.

Can I combine implicit and explicit waits?

You can, but it is risky. Because element lookup may still honor the implicit timeout inside explicit polling, total wait time can become confusing or longer than expected. Best practice in many codebases is implicit = 0 and explicit waits only.

What is FluentWait in Selenium?

FluentWait is a configurable explicit wait style where you control timeout, polling interval, and ignored exceptions. WebDriverWait is a specialized form commonly used with expected conditions. Use FluentWait when you need custom polling behavior.