You have a list of a million rows and you want to jump to row 842,013. ScrollController.jumpTo takes a pixel offset, not an index. To convert one to the other you need the total height of the 842,012 rows above your target — and a lazy list has never built them, so it does not know how tall they are. That is not an oversight. It is the entire point of a lazy list. Flutter ships no answer. The two packages that did are both dead: scrollable_positioned_list — archived by Google scroll_to_index — last published in 2022 Between them they still serve over a million downloads a month, which tells you how many apps are relying on an unmaintained solution to this. How the archived approach worked scrollable_positioned_list built a second complete list, anchored at the target index, and cross-faded from the old one to the new one. It works. It is also why jumping felt the way it did: for the duration of the transition there are two full sets of children alive in the tree, both being laid out, both painting. On a heavy row widget that is visible — a flash, a frame drop, sometimes a scrollbar that jumps twice. And it is why the package was hard to maintain. Two lists that must agree about scroll position, item extents and which one is currently authoritative is a lot of state to keep correct across Flutter versions. Why the obvious workarounds do not hold Before the real answer, it is worth walking the approaches people try first, because each fails in a way that teaches something. Fixed item extent. If every row is exactly 72 pixels, index 842,013 is at offset 60,624,936 and you are done. SliverFixedExtentList exists precisely for this and it is genuinely the fastest option. It also stops working the moment one row wraps to two lines, and "all my rows are identical forever" is a promise most products break within a month. Estimate, jump, correct. Guess an average height, jump to the estimate, then measure where you landed and adjust. This visibly hunts — the list arrives near the target and then shuffles, sometimes several times. Worse, the correction happens after paint, so the user sees the wrong content first. ensureVisible on a GlobalKey. This works beautifully and only for items that are already built. The element for index 842,013 does not exist, so there is no context to scroll to. It is the right tool for "scroll to this form field", and no tool at all for "jump into a list". Build everything. A million ListTiles is a million elements, a million render objects, and a frame budget measured in seconds. This is what shrinkWrap: true quietly does to you in some nestings, which is why it has the reputation it has. Each of these is the sensible next idea after the previous one fails. The reason none of them work is that they are all trying to compute an offset that the list fundamentally does not know. The primitive Flutter already has Viewport can nominate a centre sliver via its center property. It is there for chat-style lists that grow upwards, and it does something unusual: content before the centre sliver lays out at negative scroll offset. That is the whole solution, once you see it. Split the list in two at the anchor index: everything before the anchor goes in one sliver, laid out backwards from zero into negative offsets the anchor and everything after it go in a second sliver, marked as the centre Now scroll offset zero is the anchor. Not "approximately the anchor once we have measured our way there" — it is the anchor, by definition, because that is what nominating a centre sliver means. Jumping to index 842,013 becomes: rebuild with the anchor set to 842,013, offset zero. There is nothing to measure, nothing to estimate, and nothing above it to build. It costs the same as jumping to index 3. final controller = AnchoredListController(); AnchoredList.builder( controller: controller, itemCount: 1000000, itemBuilder: (context, index) => ListTile(title: Text('Item $index')), ); controller.jumpToIndex(842013); // same cost as jumping to item 3 One viewport, one set of children, no cross-fade. Why negative offsets are not a hack If nominating a centre sliver and laying content out at negative offsets sounds like an exotic trick, it is worth knowing it is what Flutter uses for its own reverse-scrolling lists. CustomScrollView exposes center as public API, and the framework's chat-style examples rely on exactly this behaviour. The insight this package contributes is not the primitive. It is noticing that "the offset origin can be placed anywhere" is the same problem as "jump to an arbitrary index" — and that once the origin is the target, there is no arithmetic left to do. That is why the jump is O(1) rather than merely fast. It is not that the search got quicker; there is no search. The second thing this fixes There is a related problem the same structure solves for free. You are reading a chat, or a feed, or a log. New items arrive above where you are looking. In an ordinary ListView every insertion above your position pushes your content down, because your scroll offset is measured from the top of a list that just got taller. With a centre sliver, items above the anchor live at negative offsets. Inserting one extends the list upwards, into more-negative territory. Your position is measured from the anchor, and the anchor has not moved — so your content does not move either. That is why the package is called anchored_list rather than something about jumping. Holding your place while things arrive above you is the same mechanism as jumping instantly, seen from a different angle. Where this changes what you can build Two features become straightforward that are usually quietly dropped. Deep linking into a list. A notification says "someone replied to your comment", and the comment is 4,000 items down. Ordinarily you either load a separate detail screen or you build a paginated approximation and hope. With an anchored list you open the list at that comment, and scrolling up from it works normally because the items above are real list items, lazily built as the user reaches them. Restoring scroll position properly. Saving a pixel offset and restoring it is unreliable, because content above may have changed length between sessions. Saving an index and restoring the anchor is exact — the user comes back to the item they were reading, not to a coordinate that used to contain it. Both of these usually get cut during estimation because "scroll to an arbitrary position in a long list" sounds like a week of work. It is a controller call. What it costs Honest limitations, because a list package that claims none is hiding some: Two slivers, not one. If you were relying on a single-sliver structure for something exotic, this changes it. Scrollbar geometry is estimated. A lazy list genuinely does not know its own total height, so the thumb size is a best guess that improves as more is measured. Every solution to this problem shares that limit, including the archived ones. The anchor is a rebuild, not an animation. jumpToIndex is instant by design. If you want a visible scroll across a million rows, that is a different feature and a much slower one — and one nobody actually wants, since a five-second animated scroll past 800,000 rows is not a better experience than arriving. Items above the anchor build as you scroll up, exactly as items below build as you scroll down. Jumping to index 842,013 and immediately flinging upward will build items in that direction — which is correct, but means the work is proportional to how far you scroll, not zero. On replacing an archived package If you are migrating from scrollable_positioned_list, the mental model changes in one way worth flagging. That package thinks in terms of "scroll such that item N is at alignment 0.0–1.0 within the viewport". This one thinks in terms of "item N is the origin". For the common case — put item N at the top — they express the same thing. For "put item N one third down the viewport", you are adjusting an offset from the anchor rather than passing an alignment. Try it anchored_list is on pub.dev — MIT, no dependencies, all six platforms, 160/160 pub points. The repository has a demo that jumps around a million-row list while showing the live child count. That counter is the part worth watching: it stays flat as you jump from index 3 to 842,013 and back. A flat child count during a jump is the evidence that nothing above the anchor is being built — and it is the number that distinguishes this approach from every workaround above, all of which make it spike. Originally published at devshakib.jumyn.com. I write about Flutter, Dart and the parts of shipping that are genuinely awkward — and publish the packages that came out of them at pub.dev/publishers/jumyn.com.