skip to content

In a Gatling simulation, how do you declare two minutes in which nobody arrives followed by 500 virtual users starting at the same instant?

level: middleimportance: must knowfreq 52%

answer

  1. Two steps, not one builder
  2. One buys time, one buys users
  3. The gap shifts everything declared after it
  4. nothingFor then atOnceUsers, in that order

basics

~20 s

Chain two open injection steps in one call: nothingFor for the two-minute gap, then atOnceUsers(500). The gap adds no users and only delays what follows; the surge places all 500 users on one instant and costs no time.

solid answer

~40 s

Pass two open injection steps, in order, to one registering call. In Java and Kotlin that is `scn.injectOpen(nothingFor(Duration.ofMinutes(2)), atOnceUsers(500))` with `java.time.Duration`; in JavaScript and TypeScript the registering method is still `injectOpen` but there is no `Duration` type, so the gap is a bare number of seconds, `nothingFor(120)`, or the object literal `nothingFor({ amount: 2, unit: "minutes" })`; in Scala the registering method is plain `inject` and the steps read `nothingFor(2.minutes), atOnceUsers(500)`. `nothingFor` contributes zero users and simply pushes everything after it 120 seconds later, so all 500 users are scheduled at t = 120 s. `atOnceUsers` contributes 500 users and zero duration of its own. A bare number is read as seconds everywhere, so `nothingFor(120)` is the same gap. Note that `atOnceUsers` returns a finished step — `atOnceUsers(500).during(...)` will not compile.

code

java · 24 lines
java
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import io.gatling.javaapi.http.HttpProtocolBuilder;

import java.time.Duration;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

public class QuietThenSurgeSimulation extends Simulation {

  private final HttpProtocolBuilder httpProtocol = http.baseUrl("https://example.org");

  private final ScenarioBuilder scn = scenario("surge").exec(http("home").get("/"));

  {
    setUp(
      scn.injectOpen(
        nothingFor(Duration.ofMinutes(2)),
        atOnceUsers(500)
      ).protocols(httpProtocol)
    );
  }
}

go deeper

for a junior

Be ready to write the two-step chain from memory and say which step supplies the gap and which supplies the surge, in whichever SDK the team uses.

for a middle

Be ready to explain each step's separate contribution of users and of time, and why swapping their order changes the run completely.

for a senior

Be ready to say how you would confirm from the run's own output that the gap and the surge landed where you declared them, before trusting any downstream number.

for a principal

Be ready to discuss whether surge profiles like this belong hand-written per simulation or standardised, and what makes such a profile reviewable months later.

## The two steps, and why it takes two A quiet gap followed by an instantaneous surge is not one builder — it is two open injection steps chained in order inside a single registering call. `nothingFor` supplies the gap, `atOnceUsers` supplies the surge, and the pairing works because the two steps have exactly complementary contributions. | step | users it adds | time it adds | what it does to later steps | |---|---|---|---| | `nothingFor(Duration.ofMinutes(2))` | 0 | 120 s | pushes them all 120 s later | | `atOnceUsers(500)` | 500 | 0 s | leaves their timing untouched | `nothingFor` is the **only** count-based open step that buys time without buying users, and `atOnceUsers` is the only one that buys users without buying time. Put them next to each other and you get precisely a 120-second hole followed by 500 users sharing one instant, with a total user count of 500 and an injection profile 120 seconds long. ## Writing it in each SDK The step names are the same everywhere; the registering method and the duration spelling are not. - **Java and Kotlin** — `scn.injectOpen(nothingFor(Duration.ofMinutes(2)), atOnceUsers(500))`, with `java.time.Duration` imported and `io.gatling.javaapi.core.CoreDsl.*` statically imported. - **JavaScript and TypeScript** — the same `injectOpen` call, with the steps imported from `@gatling.io/core` and the gap written as a plain number of seconds, `nothingFor(120)`. - **Scala** — the registering method is plain `inject`, and `scala.concurrent.duration._` gives you `nothingFor(2.minutes)`. A **bare number means seconds** in all of them, so `nothingFor(120)` is the same two-minute gap as `nothingFor(Duration.ofMinutes(2))`. In Java and Kotlin that is a documented overload, `nothingFor(long durationSeconds)`; in Scala it is an implicit `Int`-to-seconds conversion in `io.gatling.core.Predef`. Passing a real duration object is worth the extra characters precisely because `nothingFor(2)` and `nothingFor(Duration.ofMinutes(2))` differ by a factor of sixty and both compile. ## Three mistakes this profile invites 1. **Trying to give the surge a window.** `atOnceUsers` returns a finished injection step, not a builder, so `atOnceUsers(500).during(...)` does not compile. Only `rampUsers` and `stressPeakUsers` continue with `.during(...)`. If you actually want the 500 users spread across a window rather than piled on one instant, the step you want is `rampUsers(500).during(d)`. 2. **Expecting `nothingFor` to drain the system.** It does not wait for anything. It adds no users and shifts the schedule of the steps after it; any users started earlier keep running straight through the gap if their scenario has not finished. A gap in *arrivals* is not a gap in *activity*. 3. **Reaching for a scenario-level wait instead.** Pausing inside the scenario makes each virtual user idle mid-journey; it does not stop new users from arriving. The quiet period belongs in the injection profile, not in the action chain. ## Checking it landed Two cheap confirmations, both available without changing the profile: - **The declared total is 500.** Because every step in this family carries a fixed head-count, the population's total is a plain sum of the step counts, and `nothingFor` adds nothing to it. If the run starts a different number, the discrepancy is in the profile, not in the scenario. - **Arrivals over time should be flat at zero for two minutes and then a single spike.** Anything smeared across the window means a ramp got substituted for the instantaneous step somewhere. Neither check tells you anything about how long the *run* lasts. The injection profile is 120 seconds long; the run ends when the last of the 500 users finishes the scenario it was started into, which is a separate matter entirely. ## Two surges need two gaps Because `atOnceUsers` occupies no time of its own, stacking two of them does not give you two separated surges. `injectOpen(atOnceUsers(500), atOnceUsers(500))` is a single instant carrying 1000 users, not 500 followed later by 500. The step that creates separation is always `nothingFor`, so a profile with a quiet start, a surge, a recovery window and a second surge reads: ```java scn.injectOpen( nothingFor(Duration.ofMinutes(2)), atOnceUsers(500), nothingFor(Duration.ofMinutes(5)), atOnceUsers(500) ) ``` That profile declares 1000 users in total across a seven-minute injection window, with the second group starting seven minutes in. Each `nothingFor` shifts only what comes after it, so the gaps simply add up. ## Where the gap goes if you move it Order is meaningful. `nothingFor` shifts only what is declared **after** it, so putting it last adds its duration to the profile without ever delaying a user — a common accident when a step is appended to the end of a chain. Putting two gaps back to back simply adds their durations. And because `atOnceUsers` occupies no time, a `nothingFor` placed *after* it starts its clock at the same instant the 500 users started, not after they have finished their scenario.

  • What changes if the nothingFor step is written after the atOnceUsers step instead of before it?
    The 500 users start immediately at t = 0, and the two minutes are appended to the injection profile with nothing scheduled inside them. `nothingFor` only shifts steps declared after it, so a trailing gap delays nobody. Ordering the chain is the whole mechanism.
  • Why not put the two-minute wait inside the scenario instead of the injection profile?
    A wait inside the scenario idles each virtual user mid-journey; it does not change when users arrive. The quiet period you want is an absence of arrivals, which is an injection-profile concern. Only `nothingFor` expresses that, and it costs no users.
  • How would you spread the 500 users across thirty seconds after the gap rather than starting them together?
    Swap the second step for `rampUsers(500).during(Duration.ofSeconds(30))`, keeping `nothingFor` in front. The head-count is unchanged; only the arrival shape differs. `stressPeakUsers(500).during(...)` would spread the same 500 with a smoothed Heaviside curve instead of even spacing.

saying these in an interview costs you the question

  • Writing atOnceUsers(500).during(...) as if it accepted a window
  • Expecting nothingFor to let in-flight users finish before continuing
  • Assuming nothingFor(2) in Java means two minutes rather than two seconds
  • Putting the quiet period in the scenario chain instead of the profile