Exploratory testing isn’t manual testing. It’s a discipline of simultaneous learning, test design, and execution. You’re not working through a script someone wrote last week. You’re forming a model of the system as you go. Using what you just learned to decide what to try next, and using that to sharpen the model further. Learning and testing happen in the same breath, not in separate phases.
This isn’t a new idea, but it still hasn’t landed properly in most engineering organisations. So let’s have another go.
What exploratory testing actually is
Exploratory testing is simultaneous test design, test execution, and learning. You have a mission – what am I trying to find out about this software? You interact with the system, you observe what happens, and you use that observation to decide your next move. There’s no separation between “writing the test” and “running the test.” The test is a live, evolving thing.
This is why a good exploratory session looks nothing like ticking off a checklist. It looks like a detective following a thread. You notice the date picker behaves oddly when you go past a month, so you push on month boundaries. You notice an error message references a field name that doesn’t exist in the UI. So you go looking for where else the model and the interface have drifted apart. Each finding reshapes your model of the system, and your model reshapes what you try next.
It’s a skill, not an absence of process. Good exploratory testers use structured approaches to stay focused. Charters (a short, written mission for a session), heuristics, and session-based test management to keep the work accountable and reviewable even though it isn’t scripted. “Unscripted” doesn’t mean “unstructured.” It means the structure lives in the tester’s thinking rather than in a document written before anyone touched the software.
What it isn’t
It isn’t running through a pre-written script and marking pass or fail. That’s scripted testing, and it has its place, though that place is mostly in automation. Scripted testing is confirmatory: it checks that what you expect to be true is still true. Exploratory testing is investigative: it’s how you find out things you didn’t know to expect.
It isn’t “the testing we do before we’ve automated it.”. That framing treats exploratory testing as a stopgap, something to be eliminated as coverage improves. But automation and exploration answer different questions. Automation is brilliant at confirming known behaviour hasn’t regressed. It is structurally incapable of noticing something surprising, because it only checks for what you told it to check for. Exploratory testing is how you find the things nobody thought to write an assertion for. You don’t graduate away from it. You keep doing it, indefinitely, alongside the automation you’re building.
It isn’t unskilled either. “Just click around and see what breaks” is not exploratory testing, any more than “just write some code” is software engineering. The difference between someone randomly poking at an app and a skilled exploratory tester is the same as the difference between doodling and sketching with intent: one produces marks, the other produces information.
The trap of the “non-technical tester” myth
There is a flip side to this coin that we need to talk about. The lingering industry habit of treating exploratory testing as a low-skill task meant for people who “aren’t technical.”. For years, organisations have siloed testing as an activity that doesn’t require an understanding of architecture. Framing a lack of system knowledge as a virtue called “the user’s perspective.”
This is a disservice to both testers and the software we build.
Exploratory testing is an analytical, intellectual discipline. The depth of your exploration is directly tied to your mental model of the system. If a tester is locked out of understanding how the APIs flow, how the caching layers interact, or how data structures are altered under the hood, their ability to explore is restricted. They are trapped on the surface of the UI, clicking boxes and guessing.
We don’t need an army of un-scripted clickers to scale quality. What we actually need is to raise the baseline of system literacy.
Whitebox understanding
A great exploratory tester doesn’t need to be a senior backend engineer who can refactor a microservice. They do need to know where the seams of a modern system live. They need the technical curiosity to open the network tab, observe payload sizes, understand what a 418 error means versus a 500, and use that architectural awareness to guide their next move. That isn’t a different “mindset” that exists in isolation from engineering—it is a core engineering competency.
When we treat exploratory testing as something completely detached from technical literacy, we end up with two failure modes. We either hire crowds of people to brute-force the UI because we haven’t given them the skills to look deeper, or we let developers off the hook by pretending that exploring a feature is a mystical art they aren’t qualified to do.
The goal shouldn’t be to build a massive QA department of detached specialists. The goal is to cultivate a team—regardless of their primary role—who possess both the technical literacy to understand the architecture, and the adversarial curiosity to challenge it.

There’s no such thing as “I only do automation”
I’ve met plenty of engineers who describe themselves this way. As if automation is a self-contained discipline you can practise in isolation from exploration. It isn’t, and it can’t be. Every automated test starts as a decision about what’s worth checking and what a correct result looks like. You can’t make that decision well without first understanding how the system actually behaves. That understanding comes from exploring it.
Someone who “only does automation” is really doing one of two things. Either they’re automating checks that someone else worked out through exploration first, in which case exploration still happened, just not by them. Or they’re automating their own assumptions about how the system should behave without ever having gone and looked, in which case their suite is a beautifully engineered check of the wrong thing. Neither is automation existing independently of exploration. It’s automation quietly depending on exploration that either did or didn’t happen properly.
This matters for your own growth as an engineer, not just for test coverage. The better you get at exploratory testing, the better your automated test cases get. Exploration is where you learn which edge cases are real, which error states actually occur, and which assumptions in the spec don’t survive contact with the running system. An engineer who spends time exploring a feature before automating it writes assertions that check things that matter. An engineer who goes straight to automation writes assertions that check things that were easy to think of. Time spent exploring is time invested directly in the quality of the automation you write afterwards, not time taken away from it.
Why every engineering team needs this, regardless of what they’re building
I don’t care whether you’re building a CRM, a trading platform, or a mobile game. Every one of these things has the same underlying problem. Your requirements, your tickets, your acceptance criteria describe your intent. They do not describe what you built. The gap between those two things is where bugs live. The only way to find that gap is to go and look.
Automated tests check that the system does what you already believed it should do. They cannot tell you about the thing you didn’t think to specify, because nobody wrote an assertion for it. Exploratory testing is the all about finding out what you don’t know you don’t know. Skip it and you’re only ever validating your assumptions, never testing them.
It’s also cheap relative to what it prevents. A focused thirty-minute session, run with a clear charter, routinely surfaces things that would otherwise have shipped: an error state nobody designed for, a permission check that only fails on the second attempt, a workflow that technically works but is genuinely confusing to use. None of that shows up in a green pipeline. All of it shows up in front of a real user, real customer, or real thirty-minute exploratory session. One of those is a much better place for it to surface.
This doesn’t slow down continuous delivery, it’s part of it
I spend a lot of my time arguing for continuous delivery. I want to head off the obvious objection: doesn’t all this exploring just add friction back into a pipeline you’re trying to make fast?
No.
Exploratory testing done well doesn’t sit in front of your pipeline as a gate. It sits alongside it, informing what you do and don’t automate next and catching what automation structurally can’t.
The failure mode I actually see is the opposite one: teams treat “we have a fast pipeline” and “we do exploratory testing” as competing investments, and let the first one win by default because it’s easier to point at a dashboard for. That’s a false economy. A fast pipeline that only ever confirms known behaviour will happily ship something broken at speed. The two practices aren’t in tension, they’re solving different halves of the same problem, and you need both running continuously, not one replacing the other.
Ephemeral environments
This is a big part of why I push teams towards ephemeral test environments. Spun up per change, torn down after. For exploratory testing to sit alongside continuous delivery it needs somewhere to happen that doesn’t compete with your pipeline. It also can’t hold up the next change while someone’s in the middle of a test.
An environment that exists just for this change gives people a real system to explore. It also removes the coordination overhead of a shared environment. Environments are genuinely representative of what’s about to ship. Rather than a staging environment that’s got 7 other services that have all drifted out of sync with production.
Done this way, exploratory testing becomes another signal feeding continuous delivery rather than a checkpoint blocking it. Findings from a session either fail or pass fast, the same as any other pipeline check. When they fail, they provide valuable feedback about our automated coverage, and can guide us towards new automated tests to prevent the same class of issue next time. Either way the pipeline keeps moving. What you’re actually protecting against is the pipeline moving fast in a direction nobody’s checked.
Running sessions for different scenarios
Testing your own development work
This is the hardest version, because you already know how you intended the system to work, and that knowledge actively works against you. You’ll unconsciously avoid the paths that would break your own assumptions.
Give yourself a charter that’s deliberately adversarial to your own intent. Not “does this work” but “what would make this fail” or “what did I not build for”. Time-box it, fifteen to thirty minutes is enough for most individual pieces of work, and treat it as a distinct activity from development, not something you do in the last five minutes before opening a pull request. Test at the boundaries of what you built rather than the middle: the edges of your input validation, the states either side of the happy path, what happens when a dependency you called is slow or errors out.
It helps to deliberately test as a persona you aren’t. If you built the feature thinking about the power user, spend your session as the confused first-time user instead. If you were focused on the UI, spend some time hitting the API directly with malformed input.
Solo testing someone else’s work
This is where exploratory testing is most effective, because you don’t carry the author’s assumptions. Use that. Don’t ask the author to walk you through how it works before you start; you’ll inherit their mental model and test to it, which defeats the purpose. Read the ticket or the spec for intent, then go in cold.
Start broad to build a map of the feature, then narrow into the areas that feel risky, complex, or rushed. Complexity and risk aren’t evenly distributed; a feature usually has one or two places where most of the real risk lives, and your job in the first ten minutes is to find where those are.
Keep a running log as you go, not a formal report, just enough that you can reconstruct your path and hand off a clear repro if you find something. There are a number of free exploratory testing tools out there that help you to capture testing notes as you go.
Set a session length and a charter before you start, and if you find something that changes your understanding of the feature’s risk, it’s fine to abandon the charter and follow it, just note that you did.
Bug bashes: cross-discipline group sessions
Bug bashes solve a different problem: they bring people with genuinely different mental models into contact with the same software at the same time, and that diversity of perspective finds things a single tester, however skilled, will miss on their own.

A few things make the difference between a bug bash that generates real signal and one that generates noise:
- Give it a real charter, not just “go find bugs”. A specific area, user journey, or set of personas to focus on gets you far more useful coverage than an open invitation to poke at everything.
- Mix disciplines deliberately. Support and customer success people test differently to engineers, because they’ve seen how real users actually misuse software. Designers notice different things to backend developers. The value of a bug bash is in that variance, so make sure the room actually has it.
- Keep it short and focused. Sixty to ninety minutes is usually the sweet spot. Longer sessions lose energy and the reports get sparser as people tire.
- Make logging findings frictionless. A shared board where people drop what they find as they find it, with enough detail to reproduce, beats waiting until the end and trying to remember. Momentum matters.
- Debrief while it’s fresh. Ten minutes at the end to triage what came in, spot patterns across multiple people hitting the same area, and agree what actually needs fixing versus what’s a known limitation.
The output of a good bug bash isn’t just a list of defects. It’s a shared, lived sense across the team of where the product is actually solid and where it’s shakier than the roadmap suggested. That’s worth more than the bug list on its own.
Testing AI needs this more, not less
Everyone’s racing to ship AI-powered features right now, and I spent time leading engineering at a PE-backed AI SaaS company, so I’ve had to work this out in practice rather than in the abstract. I’ll say it plainly: AI systems need exploratory testing more than traditional software does, not less, and most teams are currently doing the opposite.
Non-determinism breaks scripted, assertion-based checking. Tests that give the same input and expect the same output, every time. An LLM-backed feature might give you a good answer, a slightly different but still good answer, or a confidently wrong one, all from the same input. You still need to automate around this: eval suites, golden datasets, regression checks on known failure modes. But those only cover the failure modes you already know to test. AI systems have a habit of failing in ways nobody on the team predicted, as the model’s behaviour is emergent rather than explicit.
Human-powered exploratory testing
Exploratory testing is how you go looking for the failure modes you haven’t thought of yet, and with AI features there are reliably more of them than you expect. If that sounds like a huge task, needing thousands of data points, you are thinking along the right lines.
Human-powered sessions here are expensive. They need to be targeted, with strong guidelines over what is under test. You need people who understand how the product and the LLM works, and are up to date on the risks associated with them. Push on the edges of what the model was actually trained or configured to handle, and pay close attention to what happens just past that edge, because that’s usually confident and wrong rather than an obvious failure. That combination, confident and wrong, is the one that does damage, because it doesn’t look like a bug to the person relying on it.
What you don’t need is thousands of user inputs from people trying to use the system like an end user, there is a much better way to get that data. Real users.
Human-in-the-loop review
This is where human-in-the-loop review earns its place, not as a compliance checkbox but as a genuine extension of exploratory testing in production. A review step where a person checks real user interactions with the system that uncovers scenarios we would never think of. Treat what reviewers flag the same way you’d treat findings from a bug bash: patterns worth feeding back into your eval suite, not one-off corrections to wave through. The goal over time is the same as anywhere else in this post, use what a human finds by exploring to make your automated checking sharper, so the human-in-the-loop step is protecting against genuinely novel failure rather than catching the same mistake over and over.
Of course reviewing every single interaction in production is just as expensive as endless hours of exploratory testing before release. There are tools, practices, and processes that need to be implemented alongside having human reviewers: random sampling, and systems like LLM-as-a-judge to take a first pass, acting as filters for what requires a human review and what does not.
When it comes to the human review, they should review everything flagged by your filters but that isn’t enough. You need to sprinkle in a few that got filtered out as ok, and even some things that weren’t in the machine reviewed samples at all. Overall this gives you the best coverage, while reducing the number of actual samples a person needs to look at.
Synthetic Persona Driven Testing
Of course, we can artificially generate user interactions too, using synthetic persona driven testing. We take the same idea of a human assuming different personas and interacting with the system with a specific goal in mind, but we automate it and scale it in a way that isn’t practical with humans alone.
Synthetic persona driven testing takes the personas and goals that we would give a human to follow, and gives them to another LLM. Your second LLM acts as the customer and with careful configuration can generate hundreds and thousands of interactions far quicker than a human can. The end result being a dataset of interactions that can be explored in much the same way as the production human-in-the-loop described above, but now we aren’t risking real users having a bad experience.
Which should you use for our AI-powered product?
If you are reading this thinking which of the 3 exploratory testing methods I mentioned above you should be using for your AI-powered product, the way to decide is simple. It is all of them. Weighted by what you’re trying to catch: human exploratory sessions for the failure modes nobody’s mapped yet, human-in-the-loop for what’s actually happening with real users, and synthetic personas for the volume neither of the other two can reach on their own. There’s a lot more to building a reliable, secure AI-powered product than these three practices alone, and that’s the subject of a talk I’m giving at MoTacon and Agile Testing Days this year (2026).
Summary
Exploratory testing isn’t the thing you do because you haven’t got round to automating it yet. It’s a distinct, skilled discipline for finding out what you don’t know about your own software, and no amount of scripted checking replaces it because scripted checking can only confirm what you already thought to ask.
If your team doesn’t build time for it, they’re only testing assumptions, not software.

Further reading
If you enjoyed this post then be sure to check out my other posts on Engineering Leadership.
Join the flock
Subscribe to get the latest posts on the messy reality of software delivery sent directly to your mailbox. I regularly share new thoughts on leadership and strategy —zero spam, just value.
Leave a Reply