Skip to content
ChartTailor
WorkServicesPricingField NotesAbout Start a build
← Pine Discoveries

Arrays keep state, but not always the state you think you saved

Pine arrays are useful for carrying levels and objects forward, but mutation makes previous-state logic easy to misread.

Arrays make Pine feel much more flexible. They let a script carry a list of levels, labels, lines, prices, timestamps, or lower-timeframe values instead of forcing everything into a single series. That flexibility is also why array bugs can be hard to see. The visual output may look plausible while the script is quietly mutating the same container over and over.

It keeps coming up because previous-state logic is where array mistakes become expensive. It is one thing to store ten levels. It is another to compare today’s ten levels with yesterday’s ten levels and know that the comparison is actually between two different states.

Why this catches people

The quick-but-wrong version is thinking of an array like a normal historical series. A series value naturally has a previous value. An array is a container, and the script can push, pop, clear, and overwrite elements inside that container. If the code saves a reference and then mutates the underlying array, the “previous” state may not be the untouched snapshot the author imagined.

This shows up in level tracking, duplicate suppression, label cleanup, and pseudo-order-book style scripts. The script wants to know whether a level is new, whether it has already been drawn, or whether it was invalidated. If the snapshot is not real, the script starts making decisions based on the current state while thinking it is looking at the old one.

The Pine bit

The pattern I prefer is to make snapshots explicit. If I need yesterday’s list, I copy the list before I mutate the current list. If I only need one derived value, I store that value as a normal series instead of dragging a whole array through the logic. The point is to decide what “previous” means in the code, not after the chart looks wrong.

Array size also matters. An empty array is not the same thing as an array containing na, and neither is the same thing as a level equal to zero. The script should guard those cases before using array.get(), array.max(), or a loop that assumes at least one element exists.

//@version=6
indicator("Array snapshot idea", overlay = true)

var float[] levels = array.new_float()

float[] beforeUpdate = array.copy(levels)

if ta.change(time("D")) != 0
    array.clear(levels)
    array.push(levels, high)

hadPreviousLevels = array.size(beforeUpdate) > 0

How I handle it in builds

When I use arrays in client indicators, I try to keep the job of each array narrow. One array stores prices. Another stores line IDs. Another stores state flags only if the relationship is unavoidable. Once a single array is expected to carry price history, drawing identity, invalidation state, and alert state, the script becomes much harder to audit.

I avoid burying array mutation inside helper functions unless the naming is very clear. A function that sounds like it is “checking” levels but also deletes them is the sort of thing that creates previous-state bugs later. Pine is already execution-order sensitive. Hidden mutation makes that worse.

Where this shows up

The simplest diagnostic is to plot or label the array size before and after the update block. If the size changes earlier than expected, the bug is usually execution order. If the size is correct but the values are wrong, the bug is usually mutation or indexing. That distinction saves time because it tells you whether to inspect the lifecycle or the contents.

I avoid mixing visual cleanup with value updates in the same loop unless the script is very small. Deleting old lines and calculating new levels are separate concerns. If they happen together, one missed branch can leave a line on the chart after its value state has already been removed, or remove a line while its value still participates in alerts.

A mature array-based script usually has boring lifecycle verbs: collect, compare, render, invalidate, clean. When those phases are visible, previous-state logic becomes much easier to trust.

How I test it

I test state-heavy code by plotting the state before and after the update step. That shows whether the script is carrying information forward intentionally or simply leaving old values alive. If a value, array, or object ID changes at the wrong time, the debug plot usually reveals it before the finished visual does.

Checks before I trust it

  • Copy arrays deliberately when a real snapshot is needed.
  • Guard array.get() and aggregate functions with array.size() checks.
  • Keep na, empty, and zero as separate meanings.
  • Separate arrays that store values from arrays that store drawing object IDs when possible.
  • Name functions honestly if they mutate array state.

What matters here is that arrays do not just add storage. They add identity and mutation. Once that is respected, they become reliable tools instead of strange boxes that sometimes remember too much and sometimes remember the wrong thing.