---
title: Collections
description: Arrays and maps in kScript, the reducer methods that iterate them, and the two limits that keep them safe.
---

<div class="flex gap-3 mb-6">
  <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-blue-50 text-blue-600 text-sm font-medium">
    Core Concept
  </span>
  <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 text-gray-600 text-sm font-medium">
    5 min read
  </span>
</div>

## What you get

kScript gives you two collection types and a set of functional methods to work them:

- **Arrays** are ordered lists. Build them with `[...]` literals and work them with `push`, `get`, `set`, `length()`, reducers, and mutators.
- **Maps** are key/value stores. Create one with `{}` and use `set`, `get`, and `size()`.
- **Arrow lambdas** (`(x) => ...`) feed the array reducers: `map`, `filter`, `reduce`, `find`, `some`, `every`, and `forEach`.

Together these replace most hand-written loops. The reducers are the idiomatic way to transform a list, and they come with iteration guards built in, so a runaway transform stops loudly instead of hanging the chart. For a deeper tour of lambdas and the reducer family, see [Lambdas & Reducers](lambdas-and-reducers.md).

## Quick reference

```javascript
var xs = [1, 2, 3]          // array literal
xs.push(4)                  // append
xs.get(0)                   // read by index
xs.set(0, 99)               // write by index
xs.length()                 // count

var m = {}                  // map literal
m.set("k", 10)              // write
m.get("k")                  // read
m.size()                    // entry count

xs.map((v) => v * 2)        // transform each element
xs.filter((v) => v > 2)     // keep matching elements
xs.reduce((sum, v) => sum + v, 0)  // fold to one value
xs.find((v) => v > 2)       // first match, || na
xs.some((v) => v > 2)       // true if any match
xs.every((v) => v > 0)      // true if all match
xs.forEach((v) => { ... })  // run a side effect per element

xs.avg()                    // numeric reducers, since 3.0.13 (in-flight)
xs.sum()
xs.median()
xs.stdev()                  // population standard deviation
xs.variance()               // population variance
xs.min()
xs.max()
xs.range()

xs.reverse()                // reverse in place and return the array
xs.first()                  // first element
xs.last()                   // last element
xs.shift()                  // remove and return first element
xs.unshift(0)               // prepend
```
## A worked example

This script exercises the whole surface in one pass: it builds an array from the current bar's prices, mutates it, runs every reducer over it, stuffs results into a map, then sums everything into one plottable score. It is dense on purpose, as a map of what fits together.

```javascript title="scripts/probes/lang-collections/collections_happy.ks"
//@version=2
define(title="Collections Happy Path", position="offchart", axis=true)

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

var prices = [trade.open, trade.high, trade.low]
prices.push(trade.close)
prices.set(0, prices.get(0) + barIndex / 10)

var transformed = prices.map((value, index) => value + index)
var filtered = transformed.filter((value) => value > trade.low)
var reduced = filtered.reduce((total, value) => total + value, 0)
var firstLarge = transformed.find((value) => value > trade.close)
var hasHigh = transformed.some((value) => value > trade.high)
var allPositive = transformed.every((value) => value > 0)
var sideEffectTotal = 0
transformed.forEach((value) => {
  sideEffectTotal += value / 1000
})

var levels = {}
levels.set("reduced", reduced)
levels.set("first", nz(firstLarge, trade.close))
levels.set("flags", (hasHigh ? 1 : 0) + (allPositive ? 1 : 0))

var score = levels.get("reduced") + levels.get("first") + levels.get("flags") + prices.length() + levels.size() + sideEffectTotal

plotLine(value=score, colors=["#2563eb"], width=2, label=["Collection score"], desc=["arrays maps lambdas and reducers"])
```


A few idioms worth lifting out of that wall:

- `prices.set(0, prices.get(0) + barIndex / 10)` reads, modifies, and writes one slot in place.
- `nz(firstLarge, trade.close)` matters because `find` returns `na` when nothing matches; `nz` supplies a fallback so the arithmetic stays finite. See [na and Color](na-and-scalar-types.md).
- The `(value, index)` lambda in `map` shows the optional index parameter; drop it when you don't need it.

What you'll see: one line whose value moves every bar, since `prices` is rebuilt from live OHLC and `barIndex` feeds into it.

## Numeric reducers and manipulators

Since 3.0.13 (in-flight), numeric arrays expose `avg`, `sum`, `median`, `stdev`, `variance`, `min`, `max`, and `range`. The statistics use population math: `variance()` divides by `n`, and `stdev()` is the square root of that population variance. Empty-array reducers return `na`. Arrays also expose `reverse`, `first`, `last`, `shift`, and `unshift` for common positional edits.

The probe below reported `ok: true`. The reducer score for `[1, 2, 3, 4]` started at `25.368033988749893`, which includes population `stdev = 1.118033988749895` and `variance = 1.25`; the empty reducer score started at `8`, proving all eight empty reducers returned `na` before the probe counted them.

```javascript title="scripts/probes/lang-collections/array_reducers_manipulators.ks"
//@version=2
define(title="Array Reducers Manipulators", position="offchart", axis=true)

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

var values = [1, 2, 3, 4]
var reducerScore = values.avg() + values.sum() + values.median() + values.stdev() + values.variance() + values.min() + values.max() + values.range()

var reversed = values.reverse()
var shifted = reversed.shift()
reversed.unshift(trade.close)
var manipulatorScore = shifted + reversed.first() + reversed.last() + reversed.length()

var empty = []
var emptyScore = (isna(empty.avg()) ? 1 : 0) + (isna(empty.sum()) ? 1 : 0) + (isna(empty.median()) ? 1 : 0) + (isna(empty.stdev()) ? 1 : 0) + (isna(empty.variance()) ? 1 : 0) + (isna(empty.min()) ? 1 : 0) + (isna(empty.max()) ? 1 : 0) + (isna(empty.range()) ? 1 : 0)

plotLine(value=reducerScore + barIndex / 1000, colors=["#2563eb"], width=2, label=["Reducers"], desc=["array numeric reducers use population statistics"])
plotLine(value=manipulatorScore, colors=["#16a34a"], width=2, label=["Manipulators"], desc=["reverse first last shift and unshift mutate or read arrays"])
plotLine(value=emptyScore + barIndex / 1000, colors=["#dc2626"], width=2, label=["Empty"], desc=["empty array reducers return na"])
```

## The two limits

Collections are bounded so a script can't exhaust memory or silently mis-type a list.

### Size ceiling: 100,000 elements

A single collection tops out at 100,000 elements. Push past it and the run stops with `Collection size limit exceeded at 9:5 (max 100000)`, pointing at the offending line. In practice you only hit this by accident, usually an unbounded `push` in a loop.

```javascript title="scripts/probes/lang-collections/collection_size_boundary.ks"
//@version=2
define(title="Collection Size Boundary", position="offchart", axis=true)

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

if (isLastBar) {
  var values = []
  for (var n = 0; n < 100001; n += 1) {
    values.push(n)
  }
}

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


### Arrays are typed

An array has one element type, inferred from how you first fill it. Mixing types is rejected at compile time. Here a numeric array gets a string pushed onto it:

```javascript title="scripts/probes/lang-collections/array_type_mismatch_boundary.ks"
//@version=2
define(title="Array Type Mismatch Boundary", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var values = [1, 2, 3]
values.push("not a number")

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


This fails to compile with `Array element type mismatch: expected number, got string at 6:13`. The takeaway: keep an array homogeneous. If you genuinely need to carry mixed data per row, reach for a [struct](user-defined-types.md) instead of forcing it into one array.
