Sort result streams with multi-key ORDER BY, including ascending and descending directions.
Page results with SKIP and LIMIT in the correct clause order.
Design stable pagination using unique tie-breakers and know when keyset pagination is preferable.
Shape API-friendly payloads with map projections and property selectors.
Expand lists into rows with UNWIND and recombine them when needed.
Use sub-projections inside map projections for nested relationship-aware payloads.
Avoid expensive deep offsets and oversized node returns in application queries.
Core concepts
ORDER BY: controlling sequence
Graphs do not store an inherent “table order.” When you MATCH
movies and people, the engine is free to return rows in any order that is correct for the
pattern. User interfaces, however, need ranked lists: newest films first, highest review
averages, alphabetical actor directories. ORDER BY sorts the
current result rows by one or more expressions before later clauses such as
SKIP, LIMIT, or another
WITH.
Sort keys may be variables already in scope, property accesses, or computed expressions.
Each key can be ASC (default) or DESC.
Null ordering is version- and configuration-sensitive in edge cases; if nulls appear in
production sorts, test the behavior on your Neo4j version and consider coalescing to a
sentinel when business rules demand it.
After aggregation (Lesson 06), you typically order by the aggregate aliases—movie counts,
average ratings—because those values only exist after the grouping boundary. You can also
order inside a WITH pipeline so a later
collect sees a deterministic sequence (for example, collect
titles newest-first).
CYPHER
MATCH (m:Movie)
RETURN m.title AS title, m.released AS released
ORDER BY released DESC, title ASCSKIP0LIMIT10
SKIP, LIMIT, and stable pages
LIMIT n keeps at most n rows.
SKIP k discards the first k rows of the ordered
stream. Together they implement classic offset pagination: page 1 is
SKIP 0 LIMIT 10, page 2 is
SKIP 10 LIMIT 10, and so on. Clause order after
RETURN is ORDER BY, then
SKIP, then LIMIT.
Stability requires a total order. If two movies share the same
released year and you order only by that year, page
boundaries can shuffle between requests. Always add a unique tie-breaker such as
title when unique in your domain, or
elementId(m) when you need a guaranteed unique secondary key.
Deep offsets are expensive: the engine still produces and discards the skipped rows.
For large catalogs, prefer keyset (seek) pagination: remember the last sort key from the
previous page and filter WHERE released < $lastReleased OR (released = $lastReleased AND title > $lastTitle)
with a small LIMIT. Offset pagination remains fine for
admin UIs and small result sets.
Map projections, UNWIND, and sub-projections
Returning entire nodes ships every property—including ones clients never display—and can
bloat payloads as models grow. Map projection syntax
m { .title, .released } builds a map with only the selected
properties. You can add aliases, literals, and computed entries:
m { .title, year: m.released, source: 'movie-graph' }.
Sub-projections nest related data. A common pattern matches a movie and its actors, then
returns m { .title, .released, actors: collect(p { .name, .born }) }.
The result is a JSON-like document ideal for REST or GraphQL resolvers without over-fetching.
UNWIND is the inverse of collect: it turns a list into rows.
Use it for parameter lists of ids, for post-processing collected values, or for generating
ranges when combined with list functions. After UNWIND, you
can MATCH from each element, filter, and re-aggregate.
ORDER BY defines sequence; SKIP moves the window; LIMIT sets page size.
Worked examples
Dataset note: Expected results use the classic Neo4j Movie graph (37 movies,
133 people) and can differ if the data changes.
01
Newest movies with stable ordering
Page through movies newest first. Secondary sort on title keeps ties deterministic across requests.
CYPHER
MATCH (m:Movie)
RETURN m.title AS title, m.released AS released
ORDER BY released DESC, title ASCSKIP0LIMIT5
Expected result
title
released
Cloud Atlas
2012
Ninja Assassin
2009
Frost/Nixon
2008
Speed Racer
2008
Charlie Wilson's War
2007
02
Map projection for a movie detail card
Build a compact document for “The Matrix” including a nested list of actor maps.
CYPHER
MATCH (m:Movie {title: 'The Matrix'})
OPTIONAL MATCH (m)<-[:ACTED_IN]-(p:Person)
WITH m, p
ORDER BY p.name
RETURN m {
.title,
.released,
.tagline,
actors: collect(p { .name, .born })
} AS movie
Expected result
movie
{title: "The Matrix", released: 1999, tagline: "Welcome to the Real World", actors: [{name: "Carrie-Anne Moss", born: 1967}, {name: "Emil Eifrem", born: 1978}, {name: "Hugo Weaving", born: 1960}, {name: "Keanu Reeves", born: 1964}, {name: "Laurence Fishburne", born: 1961}]}
03
UNWIND a title list to match movies
Applications often pass arrays of titles or ids. Unwind the parameter list, match each movie,
and return a projected map sorted by release year.
CYPHER
// :param titles => ['The Matrix', 'Top Gun', 'Cloud Atlas']UNWIND $titles AS title
MATCH (m:Movie {title: title})
RETURN m { .title, .released } AS movie
ORDER BY movie.released ASC
Expected result
movie
{title: "Top Gun", released: 1986}
{title: "The Matrix", released: 1999}
{title: "Cloud Atlas", released: 2012}
04
Second page of actors by name
Demonstrate offset pagination: page size 5, second page of people ordered by name.
CYPHER
MATCH (p:Person)
RETURN p.name AS name, p.born AS born
ORDER BY name ASCSKIP5LIMIT5
Expected result
name
born
Audrey Tautou
1976
Ben Miles
1967
Bill Paxton
1955
Bill Pullman
1953
Billy Crystal
1948
Pitfalls
Paginating without ORDER BY
SKIP/LIMIT without a total
order produces arbitrary pages. Users will see duplicates and missing rows between pages.
Non-unique sort keys
Ordering only by released is unstable when many movies share
a year. Add title or element id as a tie-breaker for production APIs.
Returning whole nodes by default
RETURN m is convenient in Browser but poor for services.
Prefer map projections so clients receive a stable, minimal contract.
Deep SKIP on hot paths
Page 5000 of a busy feed should not use SKIP 50000. Switch
to keyset pagination keyed by the sort columns you already expose.
Exercises
1. Alphabetical movie directory
Return movie titles ordered A–Z with a page size of 8 starting at offset 8 (page 2).
STARTER
MATCH (m:Movie)
RETURN m.title
// ORDER BY, SKIP, LIMIT
2. Project person cards
For people born after 1960, return a map projection with name and born, ordered by born descending, limited to 10.
STARTER
MATCH (p:Person)
WHERE p.born > 1960RETURN p { /* .name, .born */ }
3. UNWIND roles parameter
Assume $names is a list of actor names. Unwind it, match each person, and return name plus born.
STARTER
UNWIND $names AS name
MATCH (p:Person {name: name})
RETURN p.name, p.born
4. Nested director payload
For “Lana Wachowski”, return a map with name and a films list of movie map projections (title, released) ordered by released.
STARTER
MATCH (d:Person {name: 'Lana Wachowski'})-[:DIRECTED]->(m:Movie)
// ORDER BY m.released, collect movie maps, project director
Lesson quiz
Five questions. Submit once to see your score and the full answer key.