Updated on
Use List<T>. Microsoft’s own ArrayList reference says it outright: “We don’t recommend that you use the ArrayList class for new development. Instead, we recommend that you use the generic List<T> class.” ArrayList is the non-generic collection from .NET Framework 1.0, kept only so that twenty-year-old code still compiles.
The reason is type safety. ArrayList stores everything as object, so the compiler cannot stop us putting a string into a collection of numbers, and every value we add is boxed onto the heap. List<T> catches the mistake at compile time and boxes nothing.
ArrayList and List Overview
ArrayList is a non-generic collection type that allows users to store objects of any data type and it is available under System.Collections namespace. We can learn more about ArrayList from this article: Working With Collections in .NET
On the other way, a List is a generic collection type that allows users to store objects of data type as specified by the List at the time of declaration. It is a very strongly typed collection and is available under System.Collections.Generic namespace. We also have an article on List Collection in C# explaining the concepts in depth.
Both types sit on the same set of collection abstractions, and our guide to how IEnumerable, ICollection, and IList relate covers the interfaces they implement.
What Is an ArrayList in C#?
ArrayList is a non-generic collection in System.Collections that holds items as object and grows automatically as we add to it.
It came with .NET Framework 1.0, before C# had generics. Because it stores object, a single ArrayList can hold a number, a string, and a DateTime at the same time. Nothing stops us doing that by accident.
Reading from it costs a cast, and every value type we put in gets boxed onto the heap on the way in and unboxed again on the way out. That is an allocation and an indirection on every single element.
List<T> replaced it in .NET Framework 2.0 and does the same job with the element type declared up front, which the compiler then enforces for us. New code should use List<T> without exception; ArrayList is still worth recognising, because it turns up in code written before 2005.
Three lines show what that permissiveness costs us:
var list = new ArrayList { 1, "two", DateTime.Now };
var first = (int)list[0]; // cast required
var oops = (int)list[1]; // compiles, throws InvalidCastException
The collection accepts all three values, reading the first one back costs a cast, and the wrong cast on the second one still compiles. If we are maintaining code that already uses the type, we have a closer look at ArrayList and its API in a dedicated article.
ArrayList vs List in C#: What Is the Difference?
The difference is generics. List<T> fixes the element type when we declare it; ArrayList accepts anything and calls it object.
That single choice produces every other difference. Because the compiler knows a List<int> holds integers, it rejects list.Add("4") before the program runs, while the same mistake on an ArrayList compiles happily and fails later as an InvalidCastException: possibly in a different method, possibly in production.
It also produces the performance gap. Every int added to an ArrayList is boxed into a heap object and unboxed again on the way out, so a loop over a thousand numbers allocates a thousand short-lived objects for the collector to clean up. List<int> stores the integers directly, inside a single array.
And it produces the everyday ergonomics. A foreach over a List<T> gives us typed items we can use immediately; over an ArrayList it gives us object, and every use of the item needs a cast first.
Nature of Collection
By nature, ArrayList holds a heterogeneous collection of objects. This means that in a single instance of ArrayList, we can store objects of any data type. On the other hand, List is designed to hold just a homogeneous collection of objects. This means that in a single instance, we can store objects of only one data type.
Let’s see an example of ArrayList storing different object types:
using System.Collections;
var arrayList = new ArrayList();
arrayList.Add(1); // integer
arrayList.Add(2);
arrayList.Add("3"); // string
And now let’s see an example on List that allows only one kind of object:
using System.Collections.Generic; var list = new List<int>(); list.Add(1); // allows only integer values list.Add(2); list.Add(3);
Error Prone
With ArrayList, we can always expect runtime errors while accessing collections as it stores heterogeneous objects. But List is a strongly typed collection that allows objects of the type defined as T in List<T>. Hence the app will throw a compile-time error if we ever try to store anything other than what was specified for T.
Now, using the same list instance, if we try to add a string, we will face a compile error:
list.Add("4"); // Gives compile error
Boxing/Unboxing Needs
When we use ArrayList, it often requires us to box or unbox the objects that we are accessing to avoid any errors. But with List, it’s never the case.
Let’s see how:
int sum = 0;
foreach (var item in arrayList)
{
sum += Convert.ToInt32(item);
}
Console.WriteLine($"Sum is {sum}");
The output will be:
Sum is 6
As we can see with ArrayList we had to convert "3" to 3 using Convert.ToInt32() to make the computation work and avoid the runtime error.
But in the case of List, we don’t need to do that:
int sum = 0;
foreach (var item in list)
{
sum += item;
}
Console.WriteLine($"Sum is {sum}");
And the output will still be the same:
Sum is 6
As we can see the List is type-safe and we didn’t have to do any type-casting at all.
Memory Management
For value types, List is the more memory-efficient of the two, and boxing is the reason. Every int we add to an ArrayList becomes a separate object on the heap, with its own object header, and the ArrayList stores a reference to it. A List<int> stores the integers themselves, packed into a single array.
For reference-type elements the gap largely closes, because then both collections are storing references either way. The memory argument is really an argument about boxing.
Performance Efficiency
The performance difference comes from the same place: allocations. A loop that adds a thousand integers to an ArrayList also allocates a thousand short-lived boxes for the garbage collector to reclaim, and every read back costs an unboxing cast. A List<int> does neither.
Type safety on its own is not a speed feature, and both collections index into a backing array in much the same way. Where List is faster, it is faster because it does not box, which is why the effect is large for value types and small for reference types.
Usage Preferences
ArrayList can store objects of any data type, but that flexibility is not by itself a reason to pick it. If the application we’re developing targets a .NET Framework version below .NET Framework 2.0, generics do not exist yet and ArrayList is what we have. On .NET Framework 2.0 and everything after it, which is every runtime in support today, List is the choice, for all the reasons we have seen in the previous sections. The same generic-versus-non-generic split turns up elsewhere in the framework, as our comparison of Dictionary versus the non-generic Hashtable shows.
Should We Use ArrayList or List in C#?
Use List<T>. There is no scenario in new code where ArrayList is the better choice.
The only reason to touch ArrayList today is code that already uses it. Even then, swapping the type is not free (every cast, every object variable, and every method signature that accepts it has to change with it), so the sensible move is to convert at the boundary rather than everywhere at once.
If a collection genuinely has to hold unrelated types, ArrayList is still the wrong tool. List<object> expresses the same thing with generic LINQ and generic APIs available, and a common base class or interface expresses it better than either.
For the one case ArrayList was good at (a growable array of mixed primitives), the modern answer is a List<T> of a record or a discriminated shape, so the compiler still knows what came out.
| Criterion | ArrayList | List<T> |
|---|---|---|
| Namespace | System.Collections | System.Collections.Generic |
| Generic | No | Yes |
| Element type | object | T |
| Wrong-type element caught | At runtime, as InvalidCastException | At compile time |
| Boxing of value types | Yes, on every add and read | No |
| Casting on read | Required | Not required |
| Mixed types in one instance | Allowed | Not allowed |
| LINQ without a cast | No, it needs Cast<T>() or OfType<T>() | Yes |
| Available since | .NET Framework 1.0 | .NET Framework 2.0 |
| Use it when | Maintaining pre-generics code | Always, by default |
That is Microsoft’s own recommendation for the case, not a workaround: “For a heterogeneous collection of objects, use the List<Object> (in C#) or List(Of Object) (in Visual Basic) type.”
Conclusion
In this article, we learned what an ArrayList is, how it differs from List, and which of the two to reach for. The difference comes down to a single decision: List fixes the element type at declaration and ArrayList does not, and everything else, from the compile-time error to the boxing, follows from that. For new code the answer is List, without exception.
The generic collections have kept growing since, and our article on immutable collections covers the ones designed for data that should not change after we build it.
Tested with .NET 10.0.10.

This statement: “if the application we’re developing is targeting a .NET framework version .NET 2.0 or below” should probably read:
“if the application we’re developing is targeting a .NET Framework version 1.0 or 1.1, then we can use ArrayList; otherwise if we are targetting .NET Framework 2.0 or newer we should use List<T>.”
ArrayList was basically deprecated with the release of .NET Framework 2.0 and the generic collections.
Yeah, that’s fixed now. Thank you, Jeff
Good article