Understanding Ordering in Magic IndexedDB
Understanding Ordering in Magic IndexedDB
Author: Lance Wright II
Published: March 26, 2025

The Key Concept: Intent-Based Ordering vs. Return Ordering
Magic IndexedDB is not a traditional in-memory LINQ provider, nor is it a SQL engine. It’s the world’s first true LINQ-to-IndexedDB translation layer — meaning it interprets your queries as intent, decomposes and optimizes them into efficient IndexedDB calls, and then reassembles the data according to your query semantics.
Your ordering intent is always honored.
If you write .OrderByDescending(x => x.Created).Take(2).Skip(4)
, that logic is applied correctly during query translation.
But return order is not guaranteed due to performance-first streaming.
Results are often streamed or yielded as they are resolved — eliminating full memory buffering, but also not preserving final sort order.
Why This Happens: Performance & Yield-First Design
Magic IndexedDB was built around performance-first streaming:
Queries are split across indexes, cursors, and compound paths for speed.
Results start returning as soon as possible, with minimal memory overhead.
No in-memory sorting is applied before returning the data.
Full buffering would negate the performance advantages of streaming, especially on large datasets.
What About .ToList() or Non-Yielded Queries?
Some methods like .ToListAsync()
buffer results — but Magic IndexedDB applies the same behavior regardless.
Whether you use .ToListAsync()
or .AsAsyncEnumerable()
, you should assume results will not be sorted on return.
What Should I Do If I Need Ordered Results?
Sort the results in-memory after retrieval. You can rely on the correct filtering, skipping, and taking having been applied.
var result = await db.Items
.Where(x => x.Type == "Important")
.OrderByDescending(x => x.Created)
.Take(10)
.ToListAsync();
// Reapply ordering in-memory (if needed)
var sorted = result.OrderByDescending(x => x.Created).ToList();
Can I Build a Wrapper That Orders for Me?
Absolutely. Since the universal query format preserves intent, any wrapper or framework built on top can re-apply the order in-memory if needed.
Magic IndexedDB will always prioritize streaming, performance, and early returns — and leave sorting up to your app if needed. But Magic IndexedDB felt it was important to uphold consistency. Whether you utilize the ToListAsync() or the AsAsyncEnumerable() the ordering logic is the same.
Final Thoughts
This is one of the few intentional differences from LINQ-to-SQL — but it’s what gives Magic IndexedDB its high performance and low memory footprint.
Ordering is applied in logic — but not guaranteed in output. Once you understand this, you gain predictable control with incredible performance.
🔃 Ordering Behavior in Magic IndexedDB
Ordering in Magic IndexedDB is intelligently inferred and automatically optimized to reflect what an IndexedDB-native query would naturally return — without filtering and without loading full records into memory. But in complex scenarios, this inferred ordering can be unpredictable. That’s why Magic IndexedDB offers .StableOrdering()
— a deterministic, developer-controlled fallback.
⚙️ Default Ordering Behavior
Magic IndexedDB is unique in that all queries are sent as index-based, even complex ones. Filtering is never done in memory — only what is absolutely necessary is pulled, and always metadata-first.
When your query cannot be fully resolved using native IndexedDB indexing — typically because of conditions like Take()
, Skip()
, or mixed predicates — a cursor engine fallback is triggered.
During cursor fallback, Magic IndexedDB emulates ordering based on the following rule set:
📐 Cursor Engine Ordering (Default Logic)
✅ If .OrderBy(...)
is declared explicitly — it is respected as top priority
✅ All indexed fields found in the predicate (e.g. x.Age > 30 || x.FirstName == "bob"
) are added next, in the order discovered
✅ The row insert order is always used as a final tiebreaker to ensure deterministic ordering
Note: The field discovery order is typically left-to-right in simple queries. However, during optimization, expression trees may be rewritten, and indexed predicates may be reordered to maximize performance. As such, default ordering cannot be guaranteed to reflect left-to-right intent in complex predicates.
🧱 Stable Ordering: When Predictability Matters
await query
.Where(x => x.Age > 30 || x.FirstName == "bob")
.OrderBy(x => x.LastName)
.Take(5)
.StableOrdering()
.ToListAsync();
For non-Blazor users, use the stableOrdering: true
parameter instead.
🔐 What .StableOrdering() Does
Disables automatic ordering based on discovered indexed fields in the predicate
Enforces ordering only by explicitly declared .OrderBy()
fields
Always applies row insert order as the final tiebreaker
Produces predictable, deterministic results even for complex fallback scenarios
🤔 When Should I Use .StableOrdering()?
You're paginating results (.Take()
, .Skip()
) and expect consistent sort order
Your predicates are non-trivial or include ORs, nested expressions, or mixed types
You want repeatable results across sessions, environments, or large datasets
You rely on sorting by intent, not performance-based optimizations
💡 Why This Design?
Emulate native IndexedDB behavior wherever possible
Optimize memory usage by never loading full records unless absolutely required
Respect developer intent in predictable, explainable ways
Default ordering behavior is tuned for real-world performance, while .StableOrdering()
is tuned for developer certainty.
🔍 Visual Comparison
Query Type | .OrderBy(...) Present |
Indexed Fields in Predicate | Row Insert Order | .StableOrdering() |
---|---|---|---|---|
✅ Default | ✅ Used First | ✅ Appended | ✅ Always | ❌ Not Applied |
✅ Stable | ✅ Used First | ❌ Ignored | ✅ Always | ✅ Explicitly Applied |
🔚 Final Thoughts
Ordering in Magic IndexedDB is fast, smart, and memory-efficient. But when your query relies on a specific sort result, .StableOrdering()
is your guarantee.