Lists and Maps
Lists and maps are LoraDB's two composite types. Both nest, and both hold any supported value — including other lists, maps, and typed values such as temporal values and spatial points.
Lists
Ordered, heterogeneous, nestable.
RETURN [1, 2, 3]
RETURN ['a', 'b', 'c']
RETURN [1, 'two', true, null] -- heterogeneous is fine
RETURN [[1, 2], [3, 4]] -- nested
Indexing
Zero-based; negatives count from the end; out-of-range → null.
RETURN [10, 20, 30][0] -- 10
RETURN [10, 20, 30][-1] -- 30
RETURN [10, 20, 30][9] -- null
Slicing
End-exclusive. Open-ended slices work.
RETURN [1, 2, 3, 4, 5][1..3] -- [2, 3]
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]
Concatenation
RETURN [1, 2] + [3, 4] -- [1, 2, 3, 4]
RETURN [1, 2] + 3 -- [1, 2, 3]
RETURN 0 + [1, 2] -- [0, 1, 2]
Comprehensions
-- 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]
See more in List Functions → list comprehension.
Predicates (in WHERE)
MATCH (n) WHERE all(x IN n.scores WHERE x >= 0) RETURN n
MATCH (n) WHERE any(x IN n.tags WHERE x = 'VIP') RETURN n
MATCH (n) WHERE none(x IN n.scores WHERE x < 0) RETURN n
MATCH (n) WHERE single(x IN n.roles WHERE x = 'owner') RETURN n
More list functions
See List Functions for the full list — includes
size, head, tail, range, reduce, comprehensions, and pattern
comprehensions.
Parameters
MATCH (u:User)
WHERE u.id IN $ids
RETURN u
The $ids parameter must bind to a list. Lists in parameters may be
heterogeneous.
Unwinding a list into rows
Combine with UNWIND when you want
one row per element — the bulk-load idiom:
UNWIND $items AS it
CREATE (:Item {sku: it.sku, price: it.price})
Lists from aggregation
collect turns rows into a list:
MATCH (u:User)-[:OWNS]->(r:Repo)
RETURN u.name, collect(r.name) AS repos
Maps
String-keyed dictionaries.
RETURN {name: 'Ada', born: 1815}
RETURN {name: 'Ada', roles: ['admin', 'user'], profile: {tier: 'gold'}}
Key access
Dot access for static keys, bracket access for computed keys.
WITH {name: 'Ada', born: 1815} AS m
RETURN m.name, m.born, m['name']
Accessing a missing key returns null.
WITH {a: 1} AS m
RETURN m.b -- null
Build a map from variables
MATCH (u:User)
RETURN {id: u.id, name: u.name, active: u.active} AS user
Dynamic keys
WITH 'red' AS color
RETURN {[color]: 1} -- {red: 1}
Concatenation / merge
+ merges two maps, right-hand keys win:
RETURN {a: 1, b: 2} + {b: 20, c: 3}
-- {a: 1, b: 20, c: 3}
Useful when combining with SET += $patch:
MATCH (u:User {id: $id})
SET u += $patch
Maps on entities
Property maps on nodes / relationships are the same shape as literal maps:
CREATE (p:Person {name: 'Ada', born: 1815, skills: ['math', 'cs']})
MATCH (p:Person) RETURN p.name, p.skills
Map projection
Project a subset of an entity's properties into a map — useful for shaping results:
MATCH (p:Person)
RETURN p {.name, .born} -- pick keys
RETURN p {.*} -- all properties
RETURN p {.name, yob: p.born} -- rename / computed
RETURN p {.name, friends: [(p)-[:KNOWS]->(f) | f.name]}
The last form embeds a pattern comprehension inline.
keys and properties
MATCH (p:Person)
RETURN keys(p), properties(p)
-- ['born', 'name'], {born: 1815, name: 'Ada'}
Parameters
Maps bind directly — useful as a bulk-update pattern:
MATCH (u:User {id: $id})
SET u += $patch
RETURN u
Where $patch = {name: 'New Name', active: true} from the host
language.
UNWIND lists of maps
The idiomatic bulk-load pattern:
UNWIND $rows AS row
CREATE (:Person {name: row.name, born: row.born})
Serialisation
Across the HTTP boundary and in binding results:
| Value | Shape |
|---|---|
List | JSON array |
Map | JSON object |
Nested lists and maps round-trip cleanly. Typed values inside
(temporals, points, nodes) retain their kind discriminators.
Common patterns
Zip two lists
WITH ['a', 'b', 'c'] AS keys, [1, 2, 3] AS vals
RETURN [i IN range(0, size(keys) - 1) | [keys[i], vals[i]]]
Distinct list
No built-in distinct(list) helper — use collect(DISTINCT x) after
UNWIND:
UNWIND [1, 2, 2, 3, 3, 3] AS x
RETURN collect(DISTINCT x) -- [1, 2, 3]
Top-N inside a collected list
MATCH (u:User)-[:WROTE]->(p:Post)
WITH u, p ORDER BY p.published_at DESC
WITH u, collect(p.title)[..3] AS recent_three
RETURN u.name, recent_three
Map of counts
MATCH (u:User)-[:WROTE]->(p:Post)
WITH u.region AS region, count(*) AS posts
RETURN collect({region: region, posts: posts}) AS summary
Nested projection
MATCH (u:User)
RETURN u {
.id,
.name,
posts: [(u)-[:WROTE]->(p:Post) | p {.id, .title}]
}
One row per user; each row's posts is a list of small maps.
List membership vs map key check
-- IN on a list
RETURN 2 IN [1, 2, 3] -- true
-- 'in keys' on a map
WITH {a: 1, b: 2} AS m
RETURN 'a' IN keys(m) -- true
Append without duplicates
No distinct_list helper; compose with a list predicate:
WITH [1, 2, 3] AS xs, 2 AS new_x
RETURN CASE WHEN new_x IN xs THEN xs ELSE xs + new_x END
-- [1, 2, 3]
Index of first match
WITH ['a', 'b', 'c', 'd'] AS xs, 'c' AS needle
RETURN head([i IN range(0, size(xs) - 1) WHERE xs[i] = needle])
-- 2
Uses a list comprehension to filter and head
to pick the first.
Merge nested maps
+ on maps is shallow — to deep-merge, build the merged value
explicitly:
WITH {a: 1, nested: {x: 1, y: 2}} AS base,
{nested: {y: 99, z: 3}, b: 2} AS patch
RETURN base + patch + {nested: base.nested + patch.nested}
-- {a: 1, b: 2, nested: {x: 1, y: 99, z: 3}}
Edge cases
Empty list / empty map
RETURN size([]), size({}) -- 0, 0
RETURN head([]) -- null
UNWIND [] emits zero rows. Watch for unbound parameters — an unset
$rows resolves to null and UNWIND of null also emits zero rows.
See UNWIND → empty list.
Heterogeneous lists
Nothing enforces uniform element types. Use
valueType in
all(… WHERE …) if you need a guarantee.
Missing map keys
WITH {a: 1} AS m
RETURN m.b -- null
RETURN m['b'] -- null
No exception — missing keys silently return null. Be careful in
arithmetic: m.b + 1 becomes null.
Comparing maps
Equality checks compare all key/value pairs recursively. Ordering of keys doesn't matter.
RETURN {a: 1, b: 2} = {b: 2, a: 1} -- true
Limitations
- List element types are not constrained — there is no
List<Integer>constraint at creation time. - Maps don't preserve any specific key ordering in responses — rely on
explicit projection (
RETURN m {.key}) if order matters to your consumer. - No built-in
distinct(list)helper — usecollect(DISTINCT x)afterUNWIND.
See also
- List Functions — every list helper.
- Aggregation → collect.
- UNWIND / MERGE.
- RETURN → map projection.
- Properties — maps on entities.