Guide

var, varip, and Persistent State in Pine Script

Two small keywords that cause an outsized amount of confusion. Here is exactly what each one does, when to reach for it, and the realtime-bar behavior that separates them.

10 min read

Pine Script re-runs your whole script once per bar, which means an ordinary variable is born and dies on the same bar. That is fine for a calculation like close - open, but useless when you want to remember something: a running count, the price where a trade was entered, a list that grows over time. For that you need state that survives from one bar to the next, and that is what var and varip provide.

If the once-per-bar idea is not yet solid, read how Pine Script's execution model works first. This article assumes you know that a plain variable resets each bar and builds from there.

var: initialize once, keep forever

A variable declared with var is assigned its initial value only on the first bar the script executes. After that, the initialization line is skipped, and the variable keeps whatever value it last held. This is what lets you accumulate across bars.

pine
//@version=6
indicator("Counting bars since a condition")

// Count how many bars since the last time price crossed above the 50 SMA
var int barsSince = 0
crossedUp = ta.crossover(close, ta.sma(close, 50))

barsSince := crossedUp ? 0 : barsSince + 1

plot(barsSince, "bars since cross")

The var int barsSince = 0 line runs once. From then on, barsSince either resets to zero when the cross happens or increments. Note again the := reassignment operator; = would try to declare a brand new variable and fail. Without var, barsSince would be zero on every bar and the counter would never move.

var with arrays: state that grows

The most important use of var is with collections. If you declare an array without var, you get a fresh empty array every bar, which is almost never what you want. With var, the array is created once and persists, so you can push values onto it as bars form and build up history you control.

pine
//@version=6
indicator("Keep the last 20 pivot highs")

var float[] pivots = array.new_float()

ph = ta.pivothigh(5, 5)
if not na(ph)
    array.push(pivots, ph)
    if array.size(pivots) > 20
        array.shift(pivots)   // drop the oldest to cap the size

avgPivot = array.size(pivots) > 0 ? array.avg(pivots) : na
plot(avgPivot, "avg of last 20 pivot highs")

This pattern, a var array plus push and a size cap, is the backbone of most stateful indicators. For the full tour of collection types and their limits, see arrays, matrices, and maps in Pine Script.

Describe the state you want to track and PineScripter wires up the var declarations for you

varip: the realtime-bar difference

Here is where people get tripped up. On historical bars, var and varip behave identically. The difference only appears on the realtime bar, the one still forming. Recall that on the realtime bar the script reruns on every tick and rolls back its changes before each rerun. A var variable participates in that rollback: within a forming bar, it reverts to the value it had at the bar's open before recalculating.

A varip variable does not roll back. Its value carries forward across every tick, uninterrupted. That makes it the right tool only when you genuinely want to react to individual ticks, such as counting how many price updates arrive within a bar or tracking the highest price seen intrabar before the bar closes.

pine
//@version=6
indicator("var vs varip on the realtime bar")

// Rolls back each tick: effectively counts bars
var int barTicks = 0
barTicks := barTicks + 1

// Does NOT roll back: counts every realtime price update
varip int rawTicks = 0
rawTicks := rawTicks + 1

plot(barTicks, "var counter")
plot(rawTicks, "varip counter")
no keywordvarvarip
InitializedEvery barOnceOnce
Persists across barsNoYesYes
Rolled back each realtime tickn/aYesNo
Typical usePer-bar calcCounters, state, arraysTick counting, intrabar highs

Why varip is the wrong default

It is tempting to reach for varip thinking it is just a stronger var. It is not. Because it captures raw tick behavior, a varip value computed live will not match what you would get replaying the same bars historically, since history has no ticks to count. That mismatch is a form of repainting: the indicator shows one thing live and another on reload. Unless you specifically need intrabar tick behavior, use var. We cover this failure mode among others in repainting explained.

Common mistakes

The first is forgetting var entirely, so a counter or array silently resets each bar. The second is putting the initializer expression where it will be skipped after bar one; remember that only the initial assignment is skipped, not later reassignments, so keep your update logic on separate lines using :=. The third is reaching for varip to fix a problem that was really a missing var, which trades one bug for a subtler repainting bug. When in doubt, start with var and only move to varip if you have a concrete reason to process ticks.

Where a specialized tool helps

State management is one of the most common things generic AI gets wrong in Pine Script, precisely because it does not model the once-per-bar rerun. It writes counters without var, reaches for varip when var was correct, and produces indicators that look fine in a code review but reset or repaint on the chart. Because it cannot see the running result, it never notices.

PineScripter is built around Pine's actual execution model, so it applies var and varip where they belong and validates the output against TradingView instead of guessing. If you have an indicator whose values keep resetting or flickering, that is usually a state bug, and it is exactly the kind of thing you can hand to PineScripter to fix. For the bigger picture on why generic models miss this, see why ChatGPT fails at Pine Script.


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.