Locust actually makes writing load tests easier than GUI-driven tools like JMeter, because it forces you to think like a developer about your tests.

Let’s watch Locust in action.

Imagine you have a simple API endpoint, /login, that accepts a POST request with username and password.

Here’s a Locust file that simulates 100 users hitting that endpoint:

from locust import HttpUser, task, between

class WebsiteUser(HttpUser):
    wait_time = between(1, 5)  # Users wait 1-5 seconds between tasks

    @task
    def login(self):
        self.client.post("/login", json={"username": "testuser", "password": "password123"})

    # You can define multiple tasks, and Locust will randomly pick one
    # @task
    # def some_other_task(self):
    #     self.client.get("/")

You’d run this from your terminal: locust -f your_locust_file.py.

Then, you’d open your browser to http://localhost:8089 (the Locust web UI). You’d enter the number of users and the spawn rate, and hit "Start swarming."

Locust will spin up the specified number of simulated users, each running the login task, and send them to http://localhost:8089 (or whatever host you configure). The web UI will immediately show you RPS, response times, and failure rates.

The core problem Locust solves is making load testing a reproducible, version-controlled part of your development workflow, rather than a separate, manual artifact managed by a GUI. JMeter, with its XML-based test plans, can become unwieldy and difficult to integrate into CI/CD pipelines. Locust’s Python code, however, is natural to write, read, and integrate.

Internally, Locust uses Python’s gevent library to achieve massively concurrent users on a single machine. Each "user" is a green thread. This is a key difference from JMeter, which often relies on a thread-per-user model, quickly exhausting system resources.

The main levers you control in Locust are:

  • User behavior: Defined by tasks within your HttpUser (or other user types like WebSocketUser). You can write complex logic, chain requests, and use conditional branching.
  • User scaling: The locust command-line arguments (-u for users, -r for spawn rate) or the web UI control how many simulated users are active.
  • Host: The --host argument or the web UI’s host input specifies the target system.
  • Wait times: wait_time in the HttpUser class dictates the pause between user actions, simulating realistic user pacing.
  • Statistics: The web UI provides real-time metrics, but you can also get JSON reports for post-analysis.

When you define wait_time = between(1, 5), you’re telling each simulated user to pause for a random duration between 1 and 5 seconds after completing a task before picking its next task. This simulates the human tendency to not click buttons back-to-back instantaneously. It’s crucial for realistic load testing because it prevents your load generator from overwhelming the system with a constant, unrealistic stream of requests that a real user would never generate.

The next problem you’ll encounter is how to handle authentication tokens or dynamic data in your requests.

Want structured learning?

Take the full Locust course →