I’ve been playing with C# 15 quite a bit this week, and after yesterday’s post about the new with() syntax I started thinking about something I’ve always struggled with — making code both clean and actually fast.
You know how it goes. You write a quick list, start adding things in a loop, and everything works fine… until your data set grows and suddenly you’re wondering why things feel sluggish. I’ve been guilty of this more times than I care to admit.
So I decided to do a quick experiment.
I created a list with 100,000 items three different ways and timed each one. Nothing fancy, just my laptop and a stopwatch. The results surprised me a little.
Here’s what I tried:
First, the way I used to write it without thinking twice:
List<int> items = new();
for (int i = 0; i < 100_000; i++)
items.Add(i);
Second, the version where I at least tried to be responsible:
List<int> items = new(100_000);
for (int i = 0; i < 100_000; i++)
items.Add(i);
And finally, the C# 15 version:
List<int> items = [with(capacity: 100_000), ..Enumerable.Range(0, 100_000)];
Day 2 Mini-Challenge
If you have ten minutes, throw together a small console app that runs all three and prints the times. Don’t make it perfect — just something simple and honest.
Here’s the little program I ended up with:
using System.Diagnostics;
Console.WriteLine("Testing pre-allocation in C# 15");
const int Size = 100_000;
var sw = Stopwatch.StartNew();
// No capacity at all
sw.Restart();
List<int> noCap = new();
for (int i = 0; i < Size; i++) noCap.Add(i);
Console.WriteLine($"No capacity : {sw.ElapsedMilliseconds} ms");
// Old-school capacity
sw.Restart();
List<int> old = new(Size);
for (int i = 0; i < Size; i++) old.Add(i);
Console.WriteLine($"Traditional capacity : {sw.ElapsedMilliseconds} ms");
// New with() syntax
sw.Restart();
List<int> modern = [with(capacity: Size), ..Enumerable.Range(0, Size)];
Console.WriteLine($"C# 15 with(capacity) : {sw.ElapsedMilliseconds} ms");
When I ran it a few times, the with() version kept coming out on top. Sometimes the difference wasn’t huge, but it was consistently faster, and honestly the code just feels better to look at.
I didn’t expect to get this excited about something as basic as pre-allocating memory, but here we are.
— A regular developer who still geeks out over this stuff
Top comments (2)
Fun little experiment, and pre-sizing the list so it doesn't keep doubling and copying its backing array is a real win people forget. One thing worth a heads-up for anyone repeating it. A Stopwatch with millisecond resolution and no warmup run tends to hand the first block the JIT and startup cost, so whichever version runs first often looks slower for reasons that have nothing to do with the code. Running them in a shuffled order a few times, or reaching for BenchmarkDotNet, usually makes the real gap show up (or shows the last two are basically a tie). Curious whether the
with()win held once you swapped the order around?Thank you for sharing such an excellent post. I really enjoyed reading it.
I’m a Python Full-Stack Engineer with over 10 years of experience designing and building scalable software solutions for clients across a variety of industries. Along the way, I’ve learned that successful projects depend not only on strong technical execution but also on creating real business value.
With my recent contract completed, I’m exploring new opportunities to collaborate with professionals who value innovation, practical problem-solving, and long-term partnerships. I enjoy discussing ideas that combine technical excellence with sound business strategy, creating outcomes that benefit everyone involved.
I believe every connection has the potential to become something meaningful. If you're interested in exchanging ideas, exploring opportunities, or simply connecting with someone who enjoys building impactful technology, I'd be happy to hear from you.
Wishing you success in your future endeavors, and I look forward to connecting.