---
title: Limits Reference
description: The sandbox limits every kScript runs under, what each one protects, and when you will hit it.
---

Every script runs in a sandbox with fixed ceilings. They exist so one script cannot exhaust memory, hang the render, or starve other scripts on the chart. You will never notice most of them. The table below lists each limit, the exact error you get when you cross it, and the kind of script that runs into it.

## Limits at a glance

| Limit | Value | What it caps | When you hit it | Error when exceeded |
| --- | ---: | --- | --- | --- |
| Source budget | 10 weighted slots | Weighted data-source slots per script (`MAX_SOURCES_PER_SCRIPT`): native streams cost 1, heavier types can cost more, the chart spine can cost 0 | Wide multi-venue aggregation within the slot budget | `Data source limit exceeded at <line>:<col>: count=X, limit=MAX_SOURCES_PER_SCRIPT, max=10` |
| `MAX_OUTPUT_OBJECTS` | 2,000,000 | Total materialized output objects across the run | Pushing values into batched plots in a loop on long history | `Output object limit exceeded at <line>:<col> (max 2000000)` |
| `MAX_DRAWINGS_PER_KIND` | 500 | Drawings of a single kind (lines, boxes, labels, …) | Drawing a `line.new` / `box.new` / `label.new` per bar on long history | `Drawing per-kind limit exceeded at <line>:<col>: kind=line, count=501, max=500` |
| Total drawings | 3000 | Drawings across all kinds combined | Many drawing kinds at once on long history | per-kind error fires first for any single kind |
| `MAX_TABLE_CELLS` | 10000 | Cells in a single `table.new` (rows × cols) | A large dashboard or data dump table | `table.new cell limit exceeded at <line>:<col>: rows=101, cols=100, cells=10100, max=10000` |
| `MAX_COLLECTION_SIZE` | 100000 | Elements in one array or map | Accumulating every bar's value into one collection forever | `Collection size limit exceeded at <line>:<col> (max 100000)` |
| `MAX_STRUCT_CONSTRUCTION_DEPTH` | 64 | Nesting depth when building a struct/object literal | Deeply nested literal built in one expression | `Struct construction depth limit exceeded at <line>:<col> (max 64)` |
| `MAX_RECURSION_DEPTH` | 200 | Self-calls deep a user function may go | Unbounded or very deep recursion | `Recursion depth limit exceeded at <line>:<col> (max 200)` |

A few notes that save debugging time:

- **Source budget is per distinct subscription, weighted.** Identical `source(...)` calls dedupe to one. Derived series (`htf()`, math on an existing source) cost nothing because they fetch no new data. The budget is 10 weighted slots; how editor validation and the runtime surface it is being unified as part of source-slot billing, so treat 10 slots as the contract.
- **Drawings have two ceilings.** 500 of any single kind, and 3000 across all kinds. The per-kind limit almost always trips first.
- **Most limits are about loops on long history.** If you create or push something once per bar without bound, you are the script these protect against. Cap the work, or gate it behind `isLastBar`.

## Backtest grants

Strategy backtests run under their own grants, separate from the sandbox limits above (launch defaults; server-tunable):

| Tier | Replay depth | Compute budget | Runs per day |
| --- | ---: | ---: | ---: |
| Free | 1,000 bars | 10 s | 20 |
| Plus | 20,000 bars | 20 s | 200 |

Exceeding the daily allowance returns `Daily backtest limit reached. The counter resets at midnight UTC.` Sub-bar fill resolution and order-book slippage data load for plus-tier backtests; see [Strategies](../strategies/overview.md#running-a-backtest).

## Strategy declaration bounds (perps params since engine 3.0.63)

These broker parameters are validated when a strategy is compiled. Perps-specific parameters are accepted on every strategy declaration since engine 3.0.63, but only affect economics when `instrument="perps"` is active.

| Parameter | Type and bounds | Default |
| --- | --- | --- |
| `instrument` | `"spot"` or `"perps"` | `"spot"` |
| `leverage` | number greater than `0` | `1` |
| `maintenanceMarginPercent` | number at least `0` and less than `100` | `0.5` |
| `makerFeePercent` | non-negative number | `0` |
| `takerFeePercent` | non-negative number | `0` |
| `funding` | `"data"` or `"off"` | `"data"` |
| `onLiquidation` | `"continue"` or `"halt"` | `"continue"` |


## Examples

These scripts each cross one limit on purpose, so you can see the shape that triggers it.

Output objects, by pushing past 50000 values into a plot:

```javascript title="Crossing the output-object limit"
//@version=2
define(title="Max Output Objects Boundary", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)

var values = []
if (isLastBar) {
  for (var n = 0; n < 50001; n += 1) {
    values.push(trade.close + n / 1000)
  }
}

plotLine(value=values, colors=["#dc2626"], width=1, label=["Repeated"], desc=["repeated plot values"])
plotLine(value=trade.close, colors=["#2563eb"], width=2, label=["Close"], desc=["anchor for output object boundary"])
```

Table cells, with a 101 × 100 table that lands at 10100 cells:

```javascript title="Crossing the table-cell limit"
//@version=2
define(title="Max Table Cells Boundary", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)

if (isLastBar) {
  table.new("top_right", 101, 100, {bgcolor: "#ffffff"})
}

plotLine(value=trade.close, colors=["#dc2626"], width=2, label=["Close"], desc=["anchor for table cell boundary"])
```

Recursion depth, with a function that descends 201 levels:

```javascript title="Crossing the recursion-depth limit"
//@version=2
define(title="Max Recursion Depth Boundary", position="offchart", axis=true)

func descend(n) {
  if (n <= 0) {
    return 0
  }
  return 1 + descend(n - 1)
}

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)

var score = trade.close
if (isLastBar) {
  score = descend(201)
}

plotLine(value=score, colors=["#dc2626"], width=2, label=["Score"], desc=["recursion depth boundary"])
```
