Skip to main content

List Functions

Lists are first-class values. Indexing is 0-based and supports negative indices counted from the end. List functions generally return null on null input.

Overview​

GoalFunction / Syntax
Sizevalue.size(list)
First, rest, lastlist.first, list.rest, list.last
Safe index / slicelist.at(list, i), list.slice(list, from[, to])
Reversevalue.reverse(list)
Rangelist.range(start, end[, step])
Take / droplist.take, list.drop, list.take_last, list.drop_last
Append / prepend / concatlist.append, list.prepend, list.concat
Deduplicatelist.unique(list)
Per-list numeric summarieslist.sum, list.avg, list.median
Fold[`reduce(acc, x IN list
Index / slicelist[i], list[a..b]
Concatlist + list, list + x, x + list
Filter / map[`[x IN list WHERE …
Attach related entitiesPattern comprehension
Quantifyall, any, none, single
Collect rowscollect(expr)
Unwind rowsUNWIND list AS row

value.size / list.first / list.rest / list.last​

FunctionBehaviour
value.size(list)Number of elements; null → null
list.first(list)First element; empty list → null
list.rest(list)All but first; empty list → null
list.last(list)Last element; empty list → null
RETURN value.size([1, 2, 3]);            // 3
RETURN list.first([1, 2, 3]);            // 1
RETURN list.rest([1, 2, 3]);            // [2, 3]
RETURN list.last([1, 2, 3]);            // 3
RETURN list.first([]);                   // null
RETURN value.size([])                   // 0

value.size also works on strings — see String Functions → size.

reverse​

Works on lists and strings.

RETURN value.reverse([1, 2, 3]);         // [3, 2, 1]
RETURN value.reverse('abc')             // 'cba'

range​

list.range(start, end[, step]) — inclusive, integers only.

RETURN list.range(1, 5);                // [1, 2, 3, 4, 5]
RETURN list.range(0, 10, 2);            // [0, 2, 4, 6, 8, 10]
RETURN list.range(10, 1, -1);           // [10, 9, 8, …, 1]
RETURN list.range(1, 5, 0)             // null  (zero step)

Common uses​

// Pagination helper: generate page numbers
RETURN list.range(1, toInteger(math.ceil($total / $size))) AS pages

;// Simple synthetic data
UNWIND list.range(1, 100) AS i
CREATE (:Point {id: i, x: math.random() * 100, y: math.random() * 100})

Selection and Reshaping​

These functions transform one list value inside a row. They do not change row cardinality; use UNWIND when you want one output row per element.

FunctionBehaviour
list.take(xs, n)First n items; negative or zero n returns []
list.drop(xs, n)Everything after the first n items; negative or zero n returns the original list
list.take_last(xs, n)Last n items; negative or zero n returns []
list.drop_last(xs, n)Everything except the last n items; negative or zero n returns the original list
list.flatten(xs[, depth])Flattens nested lists to the given depth; default 1
list.compact(xs)Removes null elements
list.chunks(xs, size)Consecutive fixed-size chunks; the last chunk may be shorter
list.windows(xs, size[, step])Sliding fixed-size windows
list.zip(a, b)Pairs elements until the shorter list is exhausted
list.append(xs, value)Returns a new list with value added at the end
list.prepend(xs, value)Returns a new list with value added at the beginning
list.concat(a, b, ...)Concatenates two or more lists
RETURN list.take([1, 2, 3, 4], 2);              // [1, 2]
RETURN list.drop([1, 2, 3, 4], 2);              // [3, 4]
RETURN list.take_last([1, 2, 3, 4], 2);         // [3, 4]
RETURN list.drop_last([1, 2, 3, 4], 1);         // [1, 2, 3]
RETURN list.flatten([[1, 2], [3, [4]]], 2);     // [1, 2, 3, 4]
RETURN list.chunks([1, 2, 3, 4, 5], 2);         // [[1, 2], [3, 4], [5]]
RETURN list.windows([1, 2, 3, 4], 2);           // [[1, 2], [2, 3], [3, 4]]
RETURN list.zip(['a', 'b'], [1, 2, 3]);         // [['a', 1], ['b', 2]]
RETURN list.append(['a', 'b'], 'c');            // ['a', 'b', 'c']
RETURN list.prepend(['b', 'c'], 'a');           // ['a', 'b', 'c']
RETURN list.concat([1, 2], [3], [4, 5])        // [1, 2, 3, 4, 5]

Use list.sample(xs[, n]) or list.shuffle(xs) for lightweight random sampling. They are not cryptographically secure and are intended for exploration, demos, and approximate picks.

reduce​

Fold over a list, carrying an accumulator.

RETURN reduce(total = 0, x IN [1, 2, 3, 4] | total + x);    // 10
RETURN reduce(pairs = [], x IN [1, 2, 3] | pairs + [x * 2]) // [2, 4, 6]

Word frequency​

UNWIND ['red', 'red', 'blue', 'green'] AS word
RETURN word, count(*) AS n
ORDER BY n DESC

UNWIND turns each list item into a row. The aggregate groups by word, so repeated values collapse into counts without needing dynamic map keys.

Nested / layered lists​

// Flatten a list-of-lists
RETURN reduce(out = [], xs IN [[1, 2], [3, 4], [5]] | out + xs)
// [1, 2, 3, 4, 5]

reduce has no short-circuit — the whole list is visited even if the accumulator could stop early. Use any / all for short-circuit boolean questions.

Indexing and slicing​

RETURN [10, 20, 30][0];       // 10
RETURN [10, 20, 30][-1];      // 30
RETURN [10, 20, 30][5];       // null (out of range)

RETURN [1, 2, 3, 4, 5][1..3];   // [2, 3]     (end-exclusive)
RETURN [1, 2, 3, 4, 5][..2];    // [1, 2]
RETURN [1, 2, 3, 4, 5][3..];    // [4, 5]
RETURN [1, 2, 3, 4, 5][-2..]   // [4, 5]

Function Indexing And Slicing​

Use list.at(list, index) and list.slice(list, from[, to]) when a query builder needs regular function calls instead of postfix syntax. Both use the same 0-based, negative-from-end convention as list indexing.

RETURN list.at([10, 20, 30], 1);          // 20
RETURN list.at([10, 20, 30], -1);         // 30
RETURN list.at([10, 20, 30], 99);         // null

RETURN list.slice([10, 20, 30, 40], 1, 3);   // [20, 30]
RETURN list.slice([10, 20, 30, 40], -3, -1); // [20, 30]
RETURN list.slice([10, 20, 30], 2, 1)       // []

Numeric Summaries​

The list.* summary helpers work inside a single row. They skip null values and return null if a non-numeric value appears where a number is required.

FunctionBehaviour
list.sum(list)Sum of numeric items; empty/all-null list returns 0
list.avg(list)Average of numeric items; empty/all-null list returns null
list.min(list) / list.max(list)Smallest/largest comparable value
list.product(list)Product of numeric items; empty/all-null list returns 1
list.stdev(list)Sample standard deviation; fewer than two values returns null
list.median(list)Median numeric value as a float
RETURN list.sum([1, 2, 3]);       // 6
RETURN list.avg([1, 2, null, 5]); // 2.6666…
RETURN list.median([1, 10, 20]);  // 10.0
RETURN list.product([2, 3, 4])   // 24

Slicing recipes​

// Top 3
RETURN collect(x)[..3]

;// Last 3
RETURN collect(x)[-3..]

;// Second-to-fifth
RETURN collect(x)[1..5]

Concatenation​

RETURN [1, 2] + [3, 4];           // [1, 2, 3, 4]
RETURN 0 + [1, 2];                // [0, 1, 2]
RETURN [1, 2] + 3                // [1, 2, 3]

Functional Concatenation​

Use the function forms when generated queries or nested expressions are easier to build with regular calls than with +.

RETURN list.concat([1, 2], [3], [4, 5]);   // [1, 2, 3, 4, 5]
RETURN list.append([1, 2], 3);             // [1, 2, 3]
RETURN list.prepend([2, 3], 1)            // [1, 2, 3]

list.append and list.prepend can add any value, including null. list.concat requires every argument to be a list; a non-list argument returns null.

List comprehension​

// Filter
RETURN [x IN [1, 2, 3, 4] WHERE x > 2]       // [3, 4]

;// Map
RETURN [x IN [1, 2, 3] | x * 10]             // [10, 20, 30]

;// Filter + map
RETURN [x IN [1, 2, 3, 4] WHERE x > 2 | x * 10]   // [30, 40]

On node properties​

MATCH (u:User)
RETURN u.name,
     [t IN u.tags WHERE value.size(t) > 3] AS long_tags

Nested comprehension​

UNWIND [[1, 2], [3, 4], [5, 6]] AS pair
RETURN [x IN pair WHERE x % 2 = 0 | x * 10]

Pattern comprehension​

Bind a pattern and collect one value per match — inline.

MATCH (p:Person)
RETURN p.name,
     [(p)-[:KNOWS]->(f) | f.name] AS friends

With a filter:

MATCH (p:Person)
RETURN p.name,
     [(p)-[:WROTE]->(post:Post) WHERE post.published | post.title] AS posts

Returns a list per matched p — ideal when you'd otherwise add a new MATCH stage and an aggregation just to assemble "each owner's items".

A pattern comprehension whose start node is not bound by the outer row runs its pattern once per row. Put the lookup key in an inline property map, as in [(v:Person {subject: $s}) WHERE v.verified | v.name] (the key is subject), and LoraDB seeks it through an index like MATCH does, rather than scanning the label for every row. See Performance → Query shape notes.

Take top-N inline​

MATCH (u:User)
RETURN u.name,
     [(u)-[:WROTE]->(p) | p.title][..3] AS recent_titles

Pattern comprehension vs OPTIONAL MATCH​

WantUse
A list per outer row, even if emptyPattern comprehension
A row per match, with nulls for no-matchOPTIONAL MATCH
Existence only, no valuesEXISTS { … }

Predicates (in WHERE)​

These are part of WHERE but operate over lists — included here as a cross-reference.

PredicateDescription
all(x IN list WHERE pred)true if pred holds for every element
any(x IN list WHERE pred)true if pred holds for at least one
none(x IN list WHERE pred)true if pred holds for none
single(x IN list WHERE pred)true if pred holds for exactly one
MATCH (n)
WHERE all(x IN n.scores WHERE x >= 0)
RETURN n;

MATCH (n)
WHERE any(x IN n.tags WHERE x = 'featured')
RETURN n;

MATCH (n)
WHERE single(x IN n.roles WHERE x = 'owner')
RETURN n

Predicates on paths​

path.nodes(p) and path.edges(p) are lists, so path predicates work too:

MATCH p = (a)-[:FOLLOWS*1..3]->(b)
WHERE all(r IN path.edges(p) WHERE r.active)
RETURN p

See Paths.

Common patterns​

Deduplicate And Set Operations​

Use list.unique(list) to keep the first occurrence of each value inside one row. A list of integers, such as node ids from id(n), dedupes in linear time; other values are compared pair by pair. Use set-style helpers when comparing two lists.

RETURN list.unique([1, 2, 2, 3, 3, 3]);          // [1, 2, 3]
RETURN list.union([1, 2], [2, 3]);               // [1, 2, 3]
RETURN list.intersect([1, 2, 3], [2, 3, 4]);     // [2, 3]
RETURN list.diff([1, 2, 3], [2])                // [1, 3]

Sort a list​

Use list.sort(list[, 'desc']) for scalar lists in a single row. For row-level ordering, sort rows before collecting.

RETURN list.sort([3, 1, 4, 1, 5, 9, 2, 6])
// [1, 1, 2, 3, 4, 5, 6, 9]

Zip two lists​

RETURN list.zip(['a', 'b', 'c'], [1, 2, 3])
// [['a', 1], ['b', 2], ['c', 3]]

Reduce to a single value​

WITH [{k: 'a', v: 1}, {k: 'b', v: 2}] AS kvs
RETURN reduce(total = 0, kv IN kvs | total + kv.v) AS total
// 3

Running totals​

WITH [10, 20, 30, 40] AS xs
RETURN reduce(
acc = {total: 0, running: []},
x IN xs |
{
  total:   acc.total + x,
  running: acc.running + [acc.total + x]
}
).running AS running_totals
// [10, 30, 60, 100]

The accumulator is a map with two fields: total keeps the running sum between steps, running appends the new total on each step. The trailing .running picks that second field off the final map so the caller gets the sequence of totals, not the map wrapper. reduce has no short-circuit, so the full list is always visited — use it when you genuinely need every step.

Min-by on objects​

min() is an aggregate — for "pick the list element with the smallest key" within a single row, use reduce:

WITH [{name: 'a', score: 9},
    {name: 'b', score: 3},
    {name: 'c', score: 7}] AS rows
RETURN reduce(
best = rows[0],
r IN list.rest(rows) |
CASE WHEN r.score < best.score THEN r ELSE best END
) AS winner
// {name: 'b', score: 3}

Start with the first element as best, walk list.rest(rows), and on each step keep whichever of best vs. r has the smaller score. The CASE expression returns a whole map — the same shape as best — so the accumulator type is stable. Swap < for > to turn this into a max-by.

Bucket counts​

WITH [1, 7, 12, 3, 45, 9, 22] AS xs
RETURN reduce(
buckets = {small: 0, medium: 0, large: 0},
x IN xs |
CASE
  WHEN x < 10 THEN buckets + {small:  buckets.small  + 1}
  WHEN x < 30 THEN buckets + {medium: buckets.medium + 1}
  ELSE             buckets + {large:  buckets.large  + 1}
END
) AS histogram

Slice the first N of each group​

MATCH (u:User)-[:WROTE]->(p:Post)
WITH u, p ORDER BY p.published_at DESC
WITH u, collect(p)[..3] AS recent_three
RETURN u.name, [p IN recent_three | p.title]

Edge cases​

Out-of-range index​

RETURN [1, 2, 3][99];       // null
RETURN [1, 2, 3][-99]      // null

Slice with reversed bounds​

RETURN [1, 2, 3, 4][3..1]   // []   (empty, not null, not an error)

Operations on null lists​

Every list function returns null on a null input, including value.size.

RETURN value.size(null), list.first(null), list.rest(null)    // null, null, null

Heterogeneous lists​

Nothing enforces element-type uniformity.

RETURN [1, 'two', true, null]

Use a type.of check in all(… WHERE …) if you need a guarantee.

Limitations​

  • List element types are not enforced — [1, 'two', true] is a perfectly valid list. Use all(x IN list WHERE type.of(x) = 'INTEGER') if you need homogeneity.
  • reduce has no short-circuit — the whole list is visited even if a condition could stop it early.
  • list.sort only sorts directly comparable scalar values. For maps, nodes, or custom sort keys, sort rows with ORDER BY before collecting.

See also​