C#
programming
data structures
list manipulation
algorithm

Generic List - moving an item within the list

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Moving an item inside a List<T> sounds trivial until index shifting produces off-by-one bugs. This operation appears in drag-and-drop UIs, ordered workflows, ranking systems, and settings screens. A reliable implementation needs to validate indexes, handle no-op moves, and account for the way removal changes later positions.

The Basic Remove-Then-Insert Pattern

The usual way to move an item is to remove it from its current position and insert it at the target position.

csharp
1using System;
2using System.Collections.Generic;
3
4public static class ListHelpers
5{
6    public static void MoveItem<T>(List<T> list, int fromIndex, int toIndex)
7    {
8        if (list is null)
9            throw new ArgumentNullException(nameof(list));
10
11        if (fromIndex < 0 || fromIndex >= list.Count)
12            throw new ArgumentOutOfRangeException(nameof(fromIndex));
13
14        if (toIndex < 0 || toIndex >= list.Count)
15            throw new ArgumentOutOfRangeException(nameof(toIndex));
16
17        if (fromIndex == toIndex)
18            return;
19
20        T item = list[fromIndex];
21        list.RemoveAt(fromIndex);
22
23        if (toIndex > fromIndex)
24        {
25            toIndex--;
26        }
27
28        list.Insert(toIndex, item);
29    }
30}

The forward-move adjustment is the part people miss. After removal, the list becomes shorter, so a destination index to the right shifts left by one.

See the Index Shift in Practice

Here is a small example:

csharp
1var values = new List<string> { "A", "B", "C", "D" };
2
3ListHelpers.MoveItem(values, 1, 3);
4
5Console.WriteLine(string.Join(", ", values));

This prints:

text
A, C, B, D

That result surprises people at first. The call means "move the item currently at index one into the position before what was originally index three after removal adjustment." If your API semantics should mean "place it at the final absolute index," you need to document that clearly and possibly expose a different helper.

Support Moving by Value Carefully

Sometimes callers know the item value, not the index. A wrapper can handle that case.

csharp
1public static bool MoveByValue<T>(List<T> list, T item, int toIndex)
2{
3    int fromIndex = list.IndexOf(item);
4    if (fromIndex < 0)
5    {
6        return false;
7    }
8
9    ListHelpers.MoveItem(list, fromIndex, toIndex);
10    return true;
11}

This is convenient, but it only moves the first matching item. If duplicates are possible, index-based calls are safer because they remove ambiguity.

Use an Immutable Variant When Mutation Is Risky

Some codebases prefer returning a new reordered list rather than mutating the original input.

csharp
1using System.Collections.Generic;
2
3public static List<T> MoveImmutable<T>(IReadOnlyList<T> source, int fromIndex, int toIndex)
4{
5    var copy = new List<T>(source);
6    ListHelpers.MoveItem(copy, fromIndex, toIndex);
7    return copy;
8}
9
10var original = new List<int> { 10, 20, 30, 40 };
11var moved = MoveImmutable(original, 3, 1);
12
13Console.WriteLine(string.Join(", ", original));
14Console.WriteLine(string.Join(", ", moved));

This approach works well in state-driven architectures where shared mutable collections make debugging harder.

Clarify Index Semantics for UI Code

Many bugs come from disagreement about what toIndex means. In a drag-and-drop interface, the UI may report a drop location based on the original list, the partially updated list, or a slot between items. Your helper should define one interpretation and stick to it.

If the UI layer and the list helper use different semantics, the implementation may look correct in isolation while still producing wrong order in the final screen.

Performance Characteristics

For List<T>, both RemoveAt and Insert can shift many elements, so a single move is O(n). That is usually fine for normal UI lists. If you are reordering very large collections many times, measure first before reaching for a more complex data structure.

In many real applications, rendering, persistence, or network updates cost more than the in-memory list move itself.

Common Pitfalls

The most common mistake is forgetting the destination adjustment when moving an item forward in the list.

Another issue is failing to validate indexes, which turns a reorder bug into a runtime exception.

Value-based movement can also be dangerous in lists with duplicates because IndexOf only finds the first match.

Finally, teams often skip writing a shared helper and duplicate slightly different move logic in several places. That is how reorder bugs become hard to track down.

Summary

  • Moving an item in List<T> is usually implemented as remove then insert.
  • Forward moves require destination adjustment because removal shifts later indexes.
  • Validate bounds and handle no-op moves explicitly.
  • Prefer index-based APIs when duplicates make value-based movement ambiguous.
  • Use an immutable wrapper when you want predictable state updates without in-place mutation.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.