{"id":3512,"date":"2026-05-27T08:35:15","date_gmt":"2026-05-27T08:35:15","guid":{"rendered":"https:\/\/loadfocus.com\/blog\/?p=3512"},"modified":"2026-05-27T08:35:16","modified_gmt":"2026-05-27T08:35:16","slug":"what-are-virtual-users-in-load-testing","status":"publish","type":"post","link":"https:\/\/loadfocus.com\/blog\/2026\/05\/what-are-virtual-users-in-load-testing","title":{"rendered":"What are Virtual Users (VUs) in Load Testing? Definition + Examples"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\"><\/span> <span class=\"rt-time\"> 7<\/span> <span class=\"rt-label rt-postfix\">minutes read<\/span><\/span>\n<p class=\"lead\">Virtual users (VUs) are the simulated humans that hit your system during a load test. They&#8217;re the load. Where real users come from browsers and apps, VUs come from a test harness. JMeter threads, k6 worker goroutines, Locust greenlets. Each VU sends requests, waits for responses, sometimes pauses (&#8220;think time&#8221;), and repeats. Aggregate enough VUs and you get traffic that looks like a real audience.<\/p>\n\n\n\n<p>This post covers what virtual users actually are, how they differ from real users and from requests-per-second (RPS), how the major tools model them, and the common misconfigurations that make VU-based test results lie.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The quick answer<\/h2>\n\n\n\n<p>VUs are the test-harness primitive that simulates concurrent activity. They&#8217;re how you express load.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th><\/th><th>Virtual Users (VUs)<\/th><th>Real Users<\/th><th>RPS (Requests Per Second)<\/th><\/tr><\/thead><tbody><tr><td><strong>What it is<\/strong><\/td><td>A test thread\/process simulating one concurrent human<\/td><td>An actual human in front of a browser\/app<\/td><td>A rate of work the system completes<\/td><\/tr><tr><td><strong>Controlled by<\/strong><\/td><td>You (the test design)<\/td><td>Whoever your audience is<\/td><td>Emerges from VUs \u00d7 request rate \u00d7 think time<\/td><\/tr><tr><td><strong>Used for<\/strong><\/td><td>Expressing test load<\/td><td>Production traffic<\/td><td>Measuring server-side capacity<\/td><\/tr><tr><td><strong>Has think time?<\/strong><\/td><td>Optional, configurable<\/td><td>Yes, between actions<\/td><td>N\/A. It&#8217;s a rate<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>For a deeper dive into how VUs translate to RPS in practice, see <a href=\"\/blog\/2024\/06\/understanding-the-difference-between-virtual-users-and-requests-per-second-rps\">understanding the difference between virtual users and requests per second<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is a virtual user?<\/h2>\n\n\n\n<p>A virtual user is a unit of concurrent simulated activity in a load test. Mechanically, it&#8217;s typically one thread (in JMeter), one goroutine (in k6), or one greenlet\/coroutine (in Locust) that:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Logs in (or starts with a pre-authenticated session).<\/li>\n<li>Walks through a defined user flow. Checkout, search, browse, whatever the script defines.<\/li>\n<li>Sends HTTP requests, waits for responses, measures latency.<\/li>\n<li>Optionally pauses for &#8220;think time&#8221; between actions (3-10 seconds is realistic).<\/li>\n<li>Loops, either through the same flow again or exits.<\/li>\n<\/ol>\n\n\n\n<p>100 VUs running concurrently = 100 of these threads\/coroutines running in parallel. To the system under test, they look indistinguishable from 100 real users hitting the service simultaneously.<\/p>\n\n\n\n<p>The point of a <a href=\"\/glossary\/what-is-load-testing\">load test<\/a> is to express expected production load as some number of VUs, then watch how the system responds.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">VUs vs real users<\/h2>\n\n\n\n<p>VUs are useful precisely because they&#8217;re predictable in ways real users aren&#8217;t:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Repeatable.<\/strong> The same script with the same VU count produces comparable results across runs. Real-user traffic varies day-to-day.<\/li>\n<li><strong>Tunable.<\/strong> You can dial VUs from 10 to 10,000 in one script. Real-user traffic is whatever happens.<\/li>\n<li><strong>Observable.<\/strong> You define the script, so you know exactly what each VU does. Real-user behaviour is opaque.<\/li>\n<\/ul>\n\n\n\n<p>But VUs are also unrealistic in ways that bias results:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Think time is approximate.<\/strong> A real user clicks &#8220;checkout&#8221;, reads the page, hesitates, clicks &#8220;confirm&#8221;. A VU might sleep 3 seconds. The shape of think time matters for cache behaviour and connection-pool dynamics. Too short and you&#8217;re stress-testing; too long and you&#8217;re not loading the system.<\/li>\n<li><strong>VU flows are uniform.<\/strong> Real users do unpredictable things. Back buttons, abandoned carts, double-clicks. VUs follow the script you wrote, not the long tail of real behaviour.<\/li>\n<li><strong>VU data is bounded.<\/strong> Real users have unique IDs, addresses, payment methods. VUs share a CSV file. Test data variety matters for cache hit rates.<\/li>\n<\/ul>\n\n\n\n<p>The right framing: VUs are a model. Useful for capacity planning and SLO validation, less useful for catching the rare bug that only appears in real-traffic edge cases. Pair VU-based load testing with real-user monitoring (RUM) for full coverage.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">VUs vs RPS<\/h2>\n\n\n\n<p>VUs and RPS measure different things and get confused constantly:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>VUs<\/strong> = concurrent threads\/users active at the same time. A LOAD measurement.<\/li>\n<li><strong>RPS<\/strong> = requests per second the system handles. A WORK measurement.<\/li>\n<\/ul>\n\n\n\n<p>The conversion depends on per-VU request rate, which depends on think time:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A VU with 3 seconds of think time produces ~0.3 RPS (each request waits 3s before the next).<\/li>\n<li>A VU with no think time produces 10-100+ RPS (sends as fast as the network\/CPU allows).<\/li>\n<li>A VU running a complex multi-step flow might produce different RPS at different stages.<\/li>\n<\/ul>\n\n\n\n<p>Rule of thumb for typical web flows: <strong>1 VU \u2248 0.3-1 RPS<\/strong> with realistic think time. If you need to handle 1,000 RPS and your think time is 3 seconds, you need ~3,000 VUs.<\/p>\n\n\n\n<p>When someone tells you &#8220;we need to handle 5,000 users&#8221;. Clarify. Concurrent VUs? Per-second RPS? Different problems with different test designs.<\/p>\n\n\n\n<p>For the full breakdown, see <a href=\"\/blog\/2024\/06\/understanding-the-difference-between-virtual-users-and-requests-per-second-rps\">understanding the difference between virtual users and requests per second<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How JMeter models virtual users<\/h2>\n\n\n\n<p>In <a href=\"\/jmeter-load-testing\">JMeter<\/a>, each virtual user is one Java thread. A Thread Group defines:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Number of threads (users).<\/strong> The VU count.<\/li>\n<li><strong>Ramp-up period.<\/strong> How long to start all threads (10 minutes to reach 1,000 VUs = ~1.67 VUs added per second).<\/li>\n<li><strong>Loop count or duration.<\/strong> Whether each VU runs the script N times or for D seconds.<\/li>\n<\/ul>\n\n\n\n<p>The VU executes the samplers in the Thread Group sequentially: HTTP Sampler, Constant Timer (think time), next HTTP Sampler, and so on.<\/p>\n\n\n\n<p>Per-machine VU ceiling: a typical JMeter instance handles 1,000-3,000 VUs before JVM heap or thread-context-switch overhead becomes the bottleneck. Beyond that you need <a href=\"\/glossary\/what-is-scalability-testing\">distributed load testing<\/a> (multiple JMeter slaves coordinated by a master).<\/p>\n\n\n\n<p>The Stepping Thread Group plugin (and Ultimate Thread Group plugin) let you ramp VUs in steps for <a href=\"\/glossary\/what-is-capacity-testing\">capacity testing<\/a> or in sudden bursts for <a href=\"\/glossary\/what-is-spike-testing\">spike testing<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How k6 models virtual users<\/h2>\n\n\n\n<p>In <a href=\"\/k6-load-testing\">k6<\/a>, each VU is a JavaScript runtime context (powered by goja, a Go-based JS engine). VUs are much lighter-weight than JMeter threads. A typical k6 instance handles 10,000-30,000 VUs from a single machine because goroutines + goja are leaner than Java threads.<\/p>\n\n\n\n<p>k6&#8217;s <code>scenarios<\/code> configuration is more flexible than JMeter&#8217;s Thread Group:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>constant-vus<\/code>. Fixed VU count, runs for a duration. The straightforward case.<\/li>\n<li><code>ramping-vus<\/code>. VU count ramps through multiple stages. Used for stepped load shapes.<\/li>\n<li><code>constant-arrival-rate<\/code>. Fixed RPS regardless of VU count (VUs auto-scaled to maintain rate). Closer to real-world traffic modelling.<\/li>\n<li><code>ramping-arrival-rate<\/code>. RPS ramps through stages. Used for arrival-rate-based capacity testing.<\/li>\n<\/ul>\n\n\n\n<p>The arrival-rate scenarios are particularly useful when you care about RPS, not VUs (which is most of the time). You tell k6 &#8220;hold 500 RPS for 10 minutes&#8221; and it provisions whatever VU count is needed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Common VU misconfigurations<\/h2>\n\n\n\n<p>Five mistakes that produce misleading VU-based test results:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. No think time<\/h3>\n\n\n\n<p>Setting VUs without think time turns a load test into a stress test. Each VU hammers the server as fast as possible, generating unrealistically high per-VU RPS. The system fails faster than it would under real traffic.<\/p>\n\n\n\n<p>Fix: add realistic think time (3-10 seconds) between user actions, or use arrival-rate scenarios that decouple VU count from RPS.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Insufficient ramp-up<\/h3>\n\n\n\n<p>Going from 0 to 1,000 VUs in 10 seconds is a <a href=\"\/glossary\/what-is-spike-testing\">spike test<\/a>, not a load test. Most caches need warm-up time; the connection pool needs to grow; the JVM needs to JIT-compile. A 10-minute ramp-up gives the system time to stabilise at each VU level.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. Single test-data row<\/h3>\n\n\n\n<p>If all 1,000 VUs use the same userId, the database caches that user&#8217;s data perfectly. Test reports &#8220;fast performance&#8221; that won&#8217;t replicate in production where 1,000 users have 1,000 different cache misses.<\/p>\n\n\n\n<p>Fix: parameterise test data via CSV files (JMeter CSV Data Set Config, k6 papaparse). Each VU pulls a different row.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. Single source region<\/h3>\n\n\n\n<p>Running all VUs from one cloud region misses geo-distributed effects: CDN edge differences, regional rate limits, network latency. The numbers look better than real-world.<\/p>\n\n\n\n<p>Fix: distribute VUs across multiple regions. <a href=\"\/load-testing\">LoadFocus<\/a> runs distributed load tests from 25+ AWS regions with VU allocation per region (50% US East, 30% EU, 20% APAC, etc.).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. Test harness itself saturates<\/h3>\n\n\n\n<p>A laptop running 3,000 VUs hits its own CPU\/network ceiling before the system under test does. The test reports &#8220;system is fast at 3,000 VUs&#8221; when actually the laptop was the bottleneck.<\/p>\n\n\n\n<p>Fix: run from a cloud test harness, OR monitor the test machine&#8217;s CPU\/memory\/network during the run and abort if it saturates.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Best practices for VU-based load testing<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Match VU count to your actual traffic shape, not a round number.<\/strong> Don&#8217;t pick &#8220;1,000 VUs&#8221; because it sounds nice. Forecast traffic, calculate VUs from RPS \u00d7 think time, test that number.<\/li>\n<li><strong>Use arrival-rate scenarios when possible.<\/strong> &#8220;Hold 500 RPS&#8221; is more meaningful than &#8220;hold 1,500 VUs at 3s think time&#8221;. The former matches a production SLA directly.<\/li>\n<li><strong>Parameterise test data.<\/strong> Each VU should have unique inputs. Cache hit rates will lie otherwise.<\/li>\n<li><strong>Add realistic think time.<\/strong> 3-10 seconds between user actions. Without think time you&#8217;re stress-testing.<\/li>\n<li><strong>Ramp up over minutes, not seconds.<\/strong> Give caches and connection pools time to warm.<\/li>\n<li><strong>Distribute VUs across regions.<\/strong> Single-region tests miss geo-distributed bottlenecks.<\/li>\n<li><strong>Monitor the test harness.<\/strong> If the load generator saturates, the numbers lie.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Frequently asked questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s a virtual user in load testing?<\/h3>\n\n\n\n<p>A virtual user (VU) is one simulated concurrent user generated by a load testing tool like JMeter, k6, Gatling, or Locust. Mechanically, it&#8217;s a thread (JMeter) or goroutine (k6) that runs a scripted user flow against the system under test. 100 VUs = 100 simulated users hitting the system simultaneously.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s the difference between virtual users and real users?<\/h3>\n\n\n\n<p>Virtual users are scripted, repeatable, controlled by the test harness. Real users are unpredictable, varied, and hit the system in patterns you didn&#8217;t design. VUs are useful for capacity planning and SLO validation; real-user monitoring (RUM) catches the long tail of real-traffic edge cases.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s the difference between virtual users and RPS?<\/h3>\n\n\n\n<p>VUs measure concurrent activity (how many users are &#8220;in&#8221; the system at once). RPS measures rate of work (how many requests the system completes per second). With realistic think time, 1 VU produces ~0.3-1 RPS. See <a href=\"\/blog\/2024\/06\/understanding-the-difference-between-virtual-users-and-requests-per-second-rps\">the dedicated VUs-vs-RPS post<\/a> for the full breakdown.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How many virtual users can JMeter handle?<\/h3>\n\n\n\n<p>A typical JMeter instance handles 1,000-3,000 VUs before JVM heap or thread-context-switch overhead caps it. For higher VU counts you need distributed JMeter (master + slave nodes) or a managed runner like <a href=\"\/jmeter-load-testing\">LoadFocus<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How many virtual users can k6 handle?<\/h3>\n\n\n\n<p>k6 handles 10,000-30,000 VUs from a single machine. Much higher than JMeter because goroutines + goja are lighter-weight than Java threads. For higher counts, k6 supports distributed execution via the k6-operator for Kubernetes or managed runners like <a href=\"\/k6-load-testing\">LoadFocus<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Should I use think time in load tests?<\/h3>\n\n\n\n<p>Yes, for tests that simulate realistic user behaviour. 3-10 seconds of think time between actions matches typical browser-session pacing. Without think time, your &#8220;load test&#8221; is actually a stress test. Each VU hammers the server as fast as possible.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s the difference between VUs and concurrent users?<\/h3>\n\n\n\n<p>In practice they&#8217;re used interchangeably. Concurrent users is the conceptual term; VUs is the test-harness mechanism. 1,000 concurrent users = 1,000 VUs in the test.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I pick the right VU count?<\/h3>\n\n\n\n<p>Forecast peak traffic (RPS or transactions\/second), then calculate VUs needed: <code>VUs \u2248 RPS \u00d7 average think time + per-flow VU consumption<\/code>. Or use arrival-rate scenarios (k6 <code>constant-arrival-rate<\/code>, JMeter Concurrency Thread Group) that auto-scale VUs to maintain target RPS. Usually more honest than picking a VU count directly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why does my load test show fast response times but production is slow?<\/h3>\n\n\n\n<p>Most common cause: test harness misconfiguration. Common culprits. No think time (turns into stress test), single test-data row (perfect cache hits), single source region (misses geo effects), test harness saturation (load generator was the bottleneck). See the misconfigurations section above.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Bottom line<\/h2>\n\n\n\n<p>Virtual users are the test-harness primitive that simulates concurrent activity. They&#8217;re how you express load in a <a href=\"\/glossary\/what-is-load-testing\">load test<\/a>. VU count alone is meaningless without context: think time, ramp-up shape, test data variety, source regions, harness capacity all matter.<\/p>\n\n\n\n<p>For load tests that produce honest VU-to-real-world numbers, <a href=\"\/load-testing\">LoadFocus<\/a> runs JMeter and k6 from 25+ cloud regions with realistic think time, parameterised data, and distributed VU allocation. For <a href=\"\/glossary\/what-is-scalability-testing\">scalability validation<\/a> where you need to know how throughput scales with VU count, the same infrastructure runs the stepped ramps that map the curve.<\/p>\n\n\n\n<p>If your team wants engineers to design the right VU shape for your specific scenario, <a href=\"\/load-testing-services\">LoadFocus offers load testing services<\/a> where the VU model is built into the test plan as a first-class design decision.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\"><\/span> <span class=\"rt-time\"> 7<\/span> <span class=\"rt-label rt-postfix\">minutes read<\/span><\/span>Virtual users (VUs) are the simulated humans that hit your system during a load test. They&#8217;re the load. Where real users come from browsers and apps, VUs come from a test harness. JMeter threads, k6 worker goroutines, Locust greenlets. Each VU sends requests, waits for responses, sometimes pauses (&#8220;think time&#8221;), and repeats. Aggregate enough VUs&#8230;  <a href=\"https:\/\/loadfocus.com\/blog\/2026\/05\/what-are-virtual-users-in-load-testing\" class=\"more-link\" title=\"Read What are Virtual Users (VUs) in Load Testing? Definition + Examples\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":1826,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[337,9,48],"tags":[616,617,615],"class_list":["post-3512","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-api-load-testing","category-load-testing","category-test-automation","tag-concurrent-users-vs-vus","tag-jmeter-virtual-users","tag-virtual-users-in-load-testing"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/posts\/3512","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/comments?post=3512"}],"version-history":[{"count":1,"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/posts\/3512\/revisions"}],"predecessor-version":[{"id":3517,"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/posts\/3512\/revisions\/3517"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/media\/1826"}],"wp:attachment":[{"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/media?parent=3512"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/categories?post=3512"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/loadfocus.com\/blog\/wp-json\/wp\/v2\/tags?post=3512"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}