---
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 | 20 | Distinct data-source subscriptions per script | Wide multi-venue aggregation. Weighted types count for more (`orderbook` = 3) | `Script exceeds the source budget (21/20)` (build); `Data source limit exceeded at <line>:<col>: count=21, limit=MAX_SOURCES_PER_SCRIPT, max=20` (run) |
| `MAX_OUTPUT_OBJECTS` | 50000 | Total plotted/drawn objects across the run | Pushing thousands of values into a plot in a loop | `Output object limit exceeded at <line>:<col> (max 50000)` |
| `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.** Identical `source(...)` calls dedupe to one. Derived series (`htf()`, math on an existing source) cost nothing because they fetch no new data. The editor and the runtime enforce the same 20, so a script that runs is also accepted by validation.
- **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`.

## 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="scripts/probes/limits/max_output_objects_boundary.ks"
//@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="scripts/probes/limits/max_table_cells_boundary.ks"
//@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="scripts/probes/limits/max_recursion_depth_boundary.ks"
//@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"])
```

