{ifchanged $customer} compares its value with the value it had the previous time it ran in the same loop. The content renders when the value is different - and always on the first row - so in a list sorted on that field it renders once at the start of every group: the classic group heading. When the value is the same as before, the @else@ part renders instead, if the tag has one.

With several values - {ifchanged $year, $month} - the content renders when any of them changed. Sort the rows first (the sort option, or in the query): the tag only looks one row back, so an unsorted list gets a heading every time the value flips.

The memory belongs to one run of the enclosing loop - the nearest level that is not an {if}, a {case} or another {ifchanged} - so the tag may stand inside an {if} in the loop, and a loop that runs again (an inner loop on the next outer row) starts afresh. Several {ifchanged} tags in one row are kept apart by position: the second one of a row is compared with the second one of the row before.

Under the strict syntax check an {ifchanged} without a value, or without its closing tag, is an error. The {case} and {if} tags compare with fixed values; {ifchanged} compares with the row before.