{"record":{"id":"4dfd9a2ef382c7d8","repo":"DrKLO/Telegram","slug":"diffutil-hit-an-unexpected-case-while-trying-to-ca","errorCode":null,"errorMessage":"DiffUtil hit an unexpected case while trying to calculate the optimal path. Please make sure your data is not changing during the diff calculation.","messagePattern":"DiffUtil hit an unexpected case while trying to calculate the optimal path\\. Please make sure your data is not changing during the diff calculation\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"critical","filePath":"TMessagesProj/src/main/java/androidx/recyclerview/widget/DiffUtil.java","lineNumber":287,"sourceCode":"                    x--;\n                    y--;\n                }\n                backward[kOffset + backwardK] = x;\n                if (!checkInFwd && k + delta >= -d && k + delta <= d) {\n                    if (forward[kOffset + backwardK] >= backward[kOffset + backwardK]) {\n                        Snake outSnake = new Snake();\n                        outSnake.x = backward[kOffset + backwardK];\n                        outSnake.y = outSnake.x - backwardK;\n                        outSnake.size =\n                                forward[kOffset + backwardK] - backward[kOffset + backwardK];\n                        outSnake.removal = removal;\n                        outSnake.reverse = true;\n                        return outSnake;\n                    }\n                }\n            }\n        }\n        throw new IllegalStateException(\"DiffUtil hit an unexpected case while trying to calculate\"\n                + \" the optimal path. Please make sure your data is not changing during the\"\n                + \" diff calculation.\");\n    }\n\n    /**\n     * A Callback class used by DiffUtil while calculating the diff between two lists.\n     */\n    public abstract static class Callback {\n        /**\n         * Returns the size of the old list.\n         *\n         * @return The size of the old list.\n         */\n        public abstract int getOldListSize();\n\n        /**\n         * Returns the size of the new list.\n         *","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/DrKLO/Telegram/blob/45ab8f4308496e1f01026a97fcdb0d58a5274474/TMessagesProj/src/main/java/androidx/recyclerview/widget/DiffUtil.java#L269-L305","documentation":"DiffUtil computes a minimal edit script between two lists using Myers' snake algorithm with forward and backward iteration over a diagonal k-axis. The algorithm assumes it always finds an overlapping snake (forward[k] >= backward[k]) within the loop bounds for a well-formed input. The 'unexpected case' means the overlapping condition was never met — which is mathematically impossible for immutable inputs of equal total size, so it implies the list sizes or contents changed DURING the diff. This is the canonical signature of a non-stable DiffUtil.Callback whose areItemsTheSame/getOldListSize return different values across calls.","triggerScenarios":"The backing list mutated while DiffUtil.calculateDiff was running (e.g. items added/removed on the main thread during a background diff); a DiffUtil.Callback that reads a live ArrayList/ConcurrentHashMap whose size shifts between getOldListSize and areItemsTheSame calls; calling adapter.setNewList while a previous diff is still computing.","commonSituations":"Using a plain mutable List as the source for both the adapter and the DiffUtil.Callback without snapshotting; paging/infinite-scroll that appends items mid-diff; a chat-style adapter (Telegram) where new messages arrive during a DiffUtil refresh.","solutions":["Pass a defensive copy (new ArrayList<>(currentList)) to the DiffUtil.Callback so its size and contents are frozen for the duration.","Run DiffUtil.calculateDiff on a background thread and only dispatchUpdatesTo on the main thread.","Serialize list mutations: queue incoming items and apply them between diffs, never during.","If using ListAdapter (PagedList/AsyncListDiffer), ensure submitList is used rather than mutating the backing list directly."],"exampleFix":"// before\nList<Item> live = adapter.getItems(); // mutating during diff\nDiffUtil.DiffResult r = DiffUtil.calculateDiff(new DiffUtil.Callback() {\n    public int getOldListSize() { return live.size(); } // changes mid-run!\n    ...\n});\n\n// after\nList<Item> snapshot = new ArrayList<>(live);\nDiffUtil.DiffResult r = DiffUtil.calculateDiff(new DiffUtil.Callback() {\n    public int getOldListSize() { return snapshot.size(); }\n    ...\n});","handlingStrategy":"validation","validationCode":"// Snapshot lists before diffing to freeze size and contents\nList<Item> oldSnap = new ArrayList<>(current);\nList<Item> newSnap = new ArrayList<>(next);\nDiffUtil.DiffResult r = DiffUtil.calculateDiff(new DiffUtil.Callback() {\n    public int getOldListSize() { return oldSnap.size(); }\n    public int getNewListSize() { return newSnap.size(); }\n    public boolean areItemsTheSame(int o, int n) { return oldSnap.get(o).id == newSnap.get(n).id; }\n    public boolean areContentsTheSame(int o, int n) { return oldSnap.get(o).equals(newSnap.get(n)); }\n});","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass defensive copies of lists to DiffUtil.Callback.","Run calculateDiff off the main thread; dispatchUpdatesTo on the main thread.","Serialize list mutations so none occur during a diff.","Prefer AsyncListDiffer / ListAdapter which handle this internally."],"tags":["recyclerview","diffutil","concurrency","data-mutation","myers"],"backgroundTag":null,"analyzedSha":"45ab8f4308496e1f01026a97fcdb0d58a5274474","analyzedAt":"2026-08-14T05:19:30.815Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}