For years Pine Script had arrays and little else, so people bent arrays to do jobs that two other collection types now handle far more cleanly. Matrices give you a proper two-dimensional grid, and maps give you key-value lookup. Knowing which one fits a problem is the difference between readable code and a tangle of parallel arrays and index arithmetic.
All three are reference types, which means they persist naturally when declared with var and are passed by reference into functions. If the persistence part is unfamiliar, the var and varip guide covers why a collection without var resets every bar.
| Collection | Shape | Reach for it when |
|---|---|---|
| Array | One-dimensional list | You need an ordered list: recent pivots, a rolling window |
| Matrix | Two-dimensional grid | You need rows and columns: correlation grids, tabular math |
| Map | Key to value pairs | You need lookup by name: per-symbol data, labeled counters |
Arrays: the ordered list you will use most
An array is a one-dimensional, ordered collection of a single type. You create one with array.new variants, add to it with array.push, read with array.get, and there are built-in reducers like array.avg, array.max, and array.stdev that save you writing loops. The most common real pattern is a rolling window capped at a fixed size.
//@version=6
indicator("Rolling window of closes", overlay = true)
var float[] window = array.new_float()
array.push(window, close)
if array.size(window) > 20
array.shift(window) // remove oldest, keep 20
// Built-in reducers avoid manual loops
hi = array.max(window)
lo = array.min(window)
plot(hi, "window high")
plot(lo, "window low")Prefer the built-in reducers over hand-written loops wherever possible. They are faster and they sidestep the per-bar loop cost that makes scripts slow, a topic we dig into in why your Pine Script is slow.
Matrices: when you genuinely have rows and columns
A matrix is a two-dimensional structure. The honest truth is that most indicators never need one. Reach for a matrix when your data is naturally tabular: a correlation grid across several symbols, a distance matrix, or intermediate results for linear-algebra style calculations. If you find yourself keeping several arrays in lockstep and indexing them with the same variable, that is the signal a matrix, or a map of objects, would be cleaner.
//@version=6
indicator("Small matrix example")
// A 2x2 grid, filled once and read back
var matrix<float> m = matrix.new<float>(2, 2, 0.0)
if bar_index == 0
matrix.set(m, 0, 0, 1.0)
matrix.set(m, 0, 1, 2.0)
matrix.set(m, 1, 0, 3.0)
matrix.set(m, 1, 1, 4.0)
topLeft = matrix.get(m, 0, 0)
plot(topLeft)Maps: lookup by key, not by position
Maps arrived relatively recently and are underused. A map stores key-value pairs, so you look things up by a meaningful name instead of an index you have to track. This is perfect for per-symbol data in a scanner, or for counting occurrences of labeled events. Maps do not guarantee any particular ordering, so use them when the key matters and the order does not.
//@version=6
indicator("Map: per-symbol last close")
var map<string, float> lastClose = map.new<string, float>()
// In a real scanner you would loop symbols; here is the shape
map.put(lastClose, "AAPL", 100.0)
map.put(lastClose, "MSFT", 200.0)
aapl = map.get(lastClose, "AAPL")
plot(aapl)Maps pair beautifully with user-defined types: a map whose values are objects lets you keep everything about one symbol in one place. See custom types and objects for that pattern, and dynamic requests for using maps inside a multi-symbol scanner.
The limits that trip people up
Collections themselves can grow large, but the things you often build from them, drawing objects, are strictly capped. A script can hold a limited number of lines, labels, and boxes at once, and TradingView removes the oldest when you exceed the cap you set. Separately, any drawing cannot be positioned more than 9,999 bars into the past or more than 500 bars into the future relative to the current bar. If you loop over a large array creating a label per element, you will hit these limits fast.
There is also the loop budget. Any loop on a single bar must finish within 500 milliseconds or the script errors out, and because the script runs that loop on every bar, an expensive collection operation multiplies across your whole history. Favor the built-in reducers, cap your collection sizes, and avoid rebuilding a large structure from scratch every bar.
Choosing the right one
The decision is usually quick. If you need an ordered list, use an array. If your data has two dimensions, use a matrix. If you look things up by a name or key, use a map. And if each entry has several fields that belong together, wrap them in a user-defined type and store those in an array or map. Getting this choice right up front keeps your code readable and sidesteps the index-juggling that makes Pine Script scripts hard to maintain.
If you would rather describe the structure in plain English than memorize every array.*, matrix.*, and map.* function, PineScripter generates the correct collection code and the surrounding var boilerplate for you, then checks it compiles. You can try it free at PineScripter, or read how it works first.
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.