# Batching events

Batching allows a function to process multiple events in a single run. This is useful for high load systems where it's more efficient to handle a batch of events together rather than handling each event individually. Some use cases for batching include:

- Reducing the number of requests to an external API that supports batch operations.
- Creating a batch of database writes to reduce the number of transactions.
- Reducing the number of requests to your [Inngest app](/docs-markdown/apps) to improve performance or serverless costs.

## How to configure batching

```ts {{ title: "TypeScript"}}
inngest.createFunction(
  {
    id: "record-api-calls",
    batchEvents: {
      maxSize: 100,
      timeout: "5s",
      key: "event.data.user_id", // Optional: batch events by user ID
      if: "event.data.account_type == \"free\"", // Optional: Only batch events from free accounts
    },
    triggers: { event: "log/api.call" },
  },
  async ({ events, step }) => {
    // NOTE: Use the `events` argument, which is an array of event payloads
    const attrs = events.map((evt) => {
      return {
        user_id: evt.data.user_id,
        endpoint: evt.data.endpoint,
        timestamp: toDateTime(evt.ts),
        account_type: evt.data.account_type,
      };
    });

    const result = await step.run("record-data-to-db", async () => {
      return db.bulkWrite(attrs);
    });

    return { success: true, recorded: result.length };
  }
);
```

// TODO(lk) fix

```go {{ title: "Go" }}
inngestgo.CreateFunction(
	client,
	inngestgo.FunctionOpts{
		ID: "record-api-calls",
		BatchEvents: &inngestgo.ConfigBatchEvents{
			MaxSize: 100,
			Timeout: "5s",
			Key: inngestgo.StrPtr("event.data.user_id"), // Optional: batch events by user ID
      If: inngestgo.StrPtr("event.data.account_type == \"free\""), // Optional: Only batch events from free accounts 
		},
	},
	inngestgo.EventTrigger("log/api.call", nil),
	func(ctx context.Context, input inngestgo.Input[map[string]any]) (any, error) {
		// NOTE: Use the events argument, which is an array of event payloads
		events := input.Events
		attrs := make([]interface{}, len(events))
		for i, evt := range events {
			attrs[i] = map[string]interface{}{
				"user_id":   evt.Data["user_id"],
				"endpoint":  evt.Data["endpoint"],
				"timestamp": toDateTime(evt.Ts),
        "account_type": evt.Data["account_type"],
			}
		}

		_, err := step.Run(ctx, "record-data-to-db", func(ctx context.Context) (interface{}, error) {
			return db.BulkWrite(attrs)
		})
		if err != nil {
			return nil, err
		}

		return map[string]interface{}{
			"success":  true,
			"recorded": len(attrs),
		}, nil
	},
)
```

```py {{ title: "Python" }}
@inngest_client.create_function(
    fn_id="record-api-calls",
    trigger=inngest.TriggerEvent(event="log/api.call"),
    batch_events=inngest.Batch(
        max_size=100,
        timeout=datetime.timedelta(seconds=5),
        key="event.data.user_id"  # Optional: batch events by user ID
        if="event.data.account_type == \"free\"" # Optional: Only batch events from free accounts 
    ),
)
async def record_api_calls(ctx: inngest.Context):
    # NOTE: Use the events from ctx, which is an array of event payloads
    attrs = [
        {
            "user_id": evt.data.user_id,
            "endpoint": evt.data.endpoint,
            "timestamp": to_datetime(evt.ts),
            "account_type": evt.data.account_type
        }
        for evt in ctx.events
    ]

    async def record_data():
        return await db.bulk_write(attrs)

    result = await ctx.step.run("record-data-to-db", record_data)

    return {"success": True, "recorded": len(result)}
```

### Configuration reference

- `maxSize` - The maximum number of events to add to a single batch.
- `timeout` - The duration of time to wait to add events to a batch. If the batch is not full after this time, the function will be invoked with whatever events are in the current batch, regardless of size.
- `key` - An optional [expression](/docs-markdown/guides/writing-expressions) using event data to batch events by. Each unique value of the `key` will receive its own batch, enabling you to batch events by any particular key, like a user ID.
- `if` - An optional [boolean expression](/docs-markdown/guides/writing-expressions) using event data to conditionally batch events that evaluate to true on this expression.

> **Callout:** It is recommended to consider the overall batch size that you will need to process including the typical event payload size. Processing large batches can lead to memory or performance issues in your application.For system safety purposes, We also enforce a 10 MiB size limit for a batch, meaning if the size of the total number of events exceeds 10 MiB, the batch will start execution even if it's not full or has reached a timeout.
> This limit cannot be changed at the moment.

## How batching works

When batching is enabled, Inngest creates a new batch when the first event is received. The batch is filled with events until the `maxSize` is reached *or* the `timeout` is up. The function is then invoked with the full list of events in the batch. When `key` is set, Inngest will maintain a batch for each unique key, which allows you to batch events belonging to a single entity, for example a customer.

Depending on your SDK, the `events` argument will contain the full list of events within a batch. This allows you to operate on all of them within a single function.

### Conditional Batching

Conditional Batching can be enabled by providing a boolean expression in `if`.  If the expression cannot be evaluated to a boolean value or if the expression evaluates to `false`, batching will be skipped for this event and the event will be scheduled for execution immediately.

## Combining with other flow control methods

Batching does not work with all other flow control features.

### Batching with Concurrency limits

You can combine batching with [concurrency](/docs-markdown/guides/concurrency) limits. For example, setting `concurrency: { limit: 1 }` will process one batch at a time.

### Batching with Custom Concurrency keys

When a concurrency limit has a `key`, **the key is evaluated against the first event in the batch**. This means:

- If your batch contains events with different key values, only the first event's key value is used for the concurrency check
- This can lead to unintuitive behavior if events with different key values end up in the same batch, which can happen when there is no batch key or when the batch key is different from the concurrency key.

For predictable behavior, **use the same key expression for both batching and concurrency**. When the batch `key` and the concurrency `key` match, batches are naturally grouped by key value, so the concurrency limit applies per key as expected — allowing up to `n` concurrent batches per unique key.

### Incompatible flow control features

You *cannot* use batching with [idempotency](/docs-markdown/guides/handling-idempotency), [rate limiting](/docs-markdown/guides/rate-limiting), [cancellation events](/docs-markdown/guides/cancel-running-functions#cancel-with-events), or [priority](/docs-markdown/guides/priority).

## Limitations

- Check our [pricing page](https://www.inngest.com/pricing) to verify the batch size limits for each plan.

## Further reference

- [TypeScript SDK Reference](/docs-markdown/reference/functions/create#batchEvents)
- [Python SDK Reference](/docs-markdown/reference/python/functions/create#batch_events)