{
  "id": "increx",
  "title": "INCREX",
  "url": "https://redis.io/docs/latest/commands/increx/",
  "summary": "Increments the numeric value of a key by a number and sets its expiration time. Uses 0 as initial value if the key doesn't exist.",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-05-25T10:30:01-07:00",
  "page_type": "content",
  "content_hash": "48985bc47f51f11ae782f5b3142b10326b63df87a79caed5b3c7ed6774764681",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "Increments or decrements the numeric value stored at `key` by the specified amount, with optional upper/lower bounds and expiration control, in a single atomic operation.\nIf the key does not exist, it is set to `0` before performing the operation.\nAn error is returned if the key contains a value of the wrong type or a string that cannot be interpreted as a number.\n\nUnlike [`INCR`](https://redis.io/docs/latest/commands/incr) and [`INCRBY`](https://redis.io/docs/latest/commands/incrby), `INCREX` returns an array of two elements: the new value of the key after the increment, and the increment that was actually applied. When the computed result would fall outside an explicit `LBOUND`/`UBOUND` or the type limits, the default is to skip the operation and reply with `[current_value, 0]`, leaving the key and its TTL untouched. The `SATURATE` flag changes this behavior so the result is capped at the bound instead."
    },
    {
      "id": "required-arguments",
      "title": "Required arguments",
      "role": "content",
      "text": "<details open><summary><code>key</code></summary>\n\nThe name of the key to increment.\n\n</details>"
    },
    {
      "id": "optional-arguments",
      "title": "Optional arguments",
      "role": "parameters",
      "text": "<details open><summary><code>BYFLOAT increment | BYINT increment</code></summary>\n\nSpecifies the increment amount and type:\n\n* `BYFLOAT increment`: increment the value by the given long-double float. The key's existing value may be either an integer or a float, since integers can be promoted to floats losslessly. Results that would produce NaN or Infinity are rejected.\n* `BYINT increment`: increment the value by the given 64-bit signed integer. The increment may be negative to decrement the value. `BYINT` requires the key's existing value to be integer-typed; a stored float such as `\"1.5\"` cannot be parsed back as an integer. This is consistent with [`INCR`](https://redis.io/docs/latest/commands/incr)/[`INCRBY`](https://redis.io/docs/latest/commands/incrby) (integer-only) and [`INCRBYFLOAT`](https://redis.io/docs/latest/commands/incrbyfloat) (accepts both).\n\nIf neither `BYFLOAT` nor `BYINT` is specified, the key is incremented by `1` in integer mode. `BYFLOAT` and `BYINT` are mutually exclusive.\n\n</details>\n\n<details open><summary><code>LBOUND lowerbound</code></summary>\n\nSets a lower bound for the resulting value. If the computed result would fall below `lowerbound`, the operation is skipped and the reply is `[current_value, 0]` (or use the `SATURATE` flag to floor the result at `lowerbound` instead). When omitted, the bound is `LLONG_MIN` in integer mode or `-LDBL_MAX` in `BYFLOAT` mode. `LBOUND` must be less than or equal to `UBOUND` when both are specified.\n\n</details>\n\n<details open><summary><code>UBOUND upperbound</code></summary>\n\nSets an upper bound for the resulting value. If the computed result would exceed `upperbound`, the operation is skipped and the reply is `[current_value, 0]` (or use the `SATURATE` flag to cap the result at `upperbound` instead). When omitted, the bound is `LLONG_MAX` in integer mode or `LDBL_MAX` in `BYFLOAT` mode. `UBOUND` must be greater than or equal to `LBOUND` when both are specified.\n\n</details>\n\n<details open><summary><code>SATURATE</code></summary>\n\nWhen specified, an out-of-bounds result is capped at `UBOUND` or floored at `LBOUND` (or saturated to the type limits when no explicit bound is given). The second element of the reply reflects the saturated delta. An error is returned if the delta cannot be represented as a 64-bit signed integer in integer mode, or would produce Infinity in `BYFLOAT` mode. Any expiration option is still applied as specified.\n\nA bound violation includes both exceeding an explicit `LBOUND`/`UBOUND` and overflowing the type limits when no explicit bound is given.\n\n</details>\n\n<details open><summary><code>expiration flags</code></summary>\n\nThe `INCREX` command supports a set of options that modify its expiration behavior:\n\n* `EX seconds`: set the specified expiration time in seconds (a positive integer).\n* `PX milliseconds`: set the specified expiration time in milliseconds (a positive integer).\n* `EXAT unix-time-seconds`: set the specified Unix time in seconds (a positive integer) at which the key will expire.\n* `PXAT unix-time-milliseconds`: set the specified Unix time in milliseconds (a positive integer) at which the key will expire.\n* `PERSIST`: remove the expiration associated with the key.\n\nWhen no expiration option is given, the key's existing TTL (if any) is preserved.\n\n</details>\n\n<details open><summary><code>ENX</code></summary>\n\nOnly sets the TTL/expiration if the key currently has no TTL/expiration. If the key already has a TTL, the increment is still applied but the TTL is left unchanged. `ENX` can ensure that a window counter rate limiter's TTL is set only when it is created, and not reset on subsequent token requests.`ENX` must be combined with `EX`, `PX`, `EXAT`, or `PXAT` and is incompatible with `PERSIST`.\n\n</details>"
    },
    {
      "id": "examples",
      "title": "Examples",
      "role": "example",
      "text": "Default increment (by 1), starting from 0 if the key does not exist:\n\n\nDEL mykey\nINCREX mykey\nINCREX mykey\n\n\nIncrement by a specific integer using `BYINT`, including a negative increment to decrement:\n\n\nSET mykey 100\nINCREX mykey BYINT 5\nINCREX mykey BYINT -10\n\n\nIncrement by a floating-point number using `BYFLOAT`:\n\n\nSET mykey 1.5\nINCREX mykey BYFLOAT 0.25\n\n\nSet an expiration on every increment with `EX`:\n\n\nDEL mykey\nINCREX mykey BYINT 1 EX 100\nTTL mykey\n\n\nUse `ENX` to set an expiration only when the key has no existing TTL. The increment is always applied regardless:\n\n\nSET mykey 10\nINCREX mykey BYINT 1 EX 100 ENX\nTTL mykey\nSET mykey 10 EX 500\nINCREX mykey BYINT 1 EX 10 ENX\nTTL mykey\n\n\nUse `PERSIST` to remove the key's expiration while incrementing:\n\n\nSET mykey 5 EX 1000\nTTL mykey\nINCREX mykey BYINT 1 PERSIST\nTTL mykey\n\n\nCompare the default out-of-bounds behavior with `SATURATE` when the result would exceed `UBOUND`. By default the key is left untouched and the reply reports a zero delta; with `SATURATE` the result is capped at the bound and the reply reflects the saturated delta:\n\n\nSET mykey 99\nINCREX mykey BYINT 5 UBOUND 100\nSET mykey 99\nINCREX mykey BYINT 5 UBOUND 100 SATURATE"
    },
    {
      "id": "pattern-window-counter-rate-limiter",
      "title": "Pattern: window counter rate limiter",
      "role": "content",
      "text": "A common rate-limiting pattern requires atomically incrementing a counter and setting its expiration. With plain [`INCR`](https://redis.io/docs/latest/commands/incr) and [`EXPIRE`](https://redis.io/docs/latest/commands/expire), this typically requires a Lua script to be atomic.\n\n`INCREX` requires a single native command. `UBOUND` enforces the rate cap — by default, once the cap is reached the operation is skipped — and `ENX` ensures that a new window with the correct duration is created if the previous one has expired; if a window already exists, it won't be extended. When the counter has already reached the cap, `actual_increment` is `0`, giving the caller immediate feedback without extra reads or error handling:\n\n[code example]"
    },
    {
      "id": "redis-software-and-redis-cloud-compatibility",
      "title": "Redis Software and Redis Cloud compatibility",
      "role": "content",
      "text": "| Redis<br />Software | Redis<br />Cloud | <span style=\"min-width: 9em; display: table-cell\">Notes</span> |\n|:----------------------|:-----------------|:------|\n| <span title=\"Not supported\">&#x274c; Standard</span><br /><span title=\"Not supported\"><nobr>&#x274c; Active-Active</nobr></span> | <span title=\"Not supported\">&#x274c; Standard</span><br /><span title=\"Not supported\"><nobr>&#x274c; Active-Active</nobr></span> |  |"
    },
    {
      "id": "return-information",
      "title": "Return information",
      "role": "returns",
      "text": "**RESP2:**\n\n[Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): a two-element array:\n\n1. **New value** — the value of the key after the increment, or the unchanged current value when an out-of-bounds result caused the operation to be skipped.\n2. **Actual increment** — the increment that was actually applied. May differ from the requested increment when `SATURATE` caps the result at a bound, and is always `0` when an out-of-bounds result caused the operation to be skipped.\n\nBoth elements are [Integer replies](https://redis.io/docs/latest/develop/reference/protocol-spec#integers) in integer mode (default or `BYINT`), or [Bulk string replies](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings) representing the float values in `BYFLOAT` mode.\n\n**RESP3:**\n\n[Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): a two-element array:\n\n1. **New value** — the value of the key after the increment, or the unchanged current value when an out-of-bounds result caused the operation to be skipped.\n2. **Actual increment** — the increment that was actually applied. May differ from the requested increment when `SATURATE` caps the result at a bound, and is always `0` when an out-of-bounds result caused the operation to be skipped.\n\nBoth elements are [Integer replies](https://redis.io/docs/latest/develop/reference/protocol-spec#integers) in integer mode (default or `BYINT`), or [Double replies](https://redis.io/docs/latest/develop/reference/protocol-spec#doubles) in `BYFLOAT` mode."
    }
  ],
  "examples": [
    {
      "id": "pattern-window-counter-rate-limiter-ex0",
      "language": "python",
      "code": "new_val, actual_incr = redis.execute_command(\n    \"INCREX\", f\"ratelimit:{user_id}\",\n    \"BYINT\", 1, \"UBOUND\", 100,\n    \"EX\", 60, \"ENX\",\n)\nif actual_incr == 0:\n    reject_request()  # rate limit exceeded",
      "section_id": "pattern-window-counter-rate-limiter"
    }
  ]
}
