Interview questions

C# Append to Array: The Interview Answer on Performance and Tradeoffs

August 6, 2025Updated May 10, 202618 min read
What No One Tells You About c append to array and Interview Performance

Master the C# append to array interview question by explaining fixed-size arrays, Array.Resize copy costs, and when List<T> is the better choice.

Most candidates who freeze on the array append question in a C# interview aren't missing the answer — they're missing the explanation. They know you can call `Array.Resize` or switch to a `List<T>`. What they can't do fluently is explain why the choice matters, and that's exactly what the interviewer is listening for. The c append array interview performance question is really a test of whether you understand memory allocation, copy costs, and the difference between a convenient API and an efficient one.

The syntax workaround takes thirty seconds to learn. The performance story takes a bit longer — but once you have it, you can answer the question, handle the follow-up, and explain the tradeoff in a design review without bluffing. That's what this guide is for.

What Interviewers Are Really Testing With the Append Question

They Are Not Testing Syntax First

When a senior engineer asks "how do you append to an array in C#?", they already know you can Google the method name. What they're probing is whether you understand why the language works this way — that arrays in C# are fixed-size by design, that there is no in-place append, and that every workaround involves either a new allocation or a different data structure entirely.

The question surfaces constantly in technical screens because it sits at the intersection of several things that matter in production code: memory layout, allocation cost, garbage collection pressure, and the difference between convenience and performance. A candidate who answers "just use `Array.Resize`" and stops there has demonstrated they know the API. A candidate who follows that with "but that copies the whole array, so if you're doing it repeatedly you're paying O(N) on every append" has demonstrated they understand the system.

That second answer is what gets you past the screening round.

What This Looks Like in Practice

Imagine two candidates answering the same prompt: "How would you append an element to an existing array in C#?"

Candidate A says: "You can use `Array.Resize` to increase the length, then assign the new element to the last index."

Candidate B says: "Arrays in C# are fixed-size, so you can't append in place. `Array.Resize` allocates a new array and copies the old contents over, which is fine once but expensive if you're doing it in a loop. If you need frequent appends, `List<T>` is the right tool — it manages capacity growth internally so you're not paying a full copy on every add."

Candidate A answered the question. Candidate B answered the question and demonstrated that they think about the consequences of their choices. Interviewers who ask this question — and experienced engineers who've reviewed production code where someone put `Array.Resize` inside a tight loop — have seen the damage that comes from not thinking one level deeper. According to Microsoft's own documentation on arrays, arrays have a fixed length after instantiation. That constraint isn't a limitation to work around — it's a design decision to understand.

Stop Treating C# Arrays Like They Can Grow

Why Arrays Cannot Be Appended to in Place

A C# array is a contiguous block of memory with a fixed length set at the moment of allocation. Once it exists, that length is immutable — `myArray.Length` does not change, and there is no method that extends the block in place. The runtime doesn't reserve extra space at the end of the array "just in case." The memory immediately after the array belongs to something else.

This is not a quirk or an oversight. It's the same constraint you find in arrays across most languages with explicit memory management, and it exists because fixed-size contiguous allocation is extremely efficient for indexed access. You get O(1) reads and writes by index. You give up the ability to grow without copying.

When you try to append to an array in C#, you're not actually appending — you're creating a new, larger array and discarding the old one.

What This Looks Like in Practice

What looks like a resize is actually a replacement. After `Array.Resize`, the variable `original` points to a brand new array of length 4. The old array — the one with length 3 — is now eligible for garbage collection. If you had another variable pointing to the original array, it still points to the old one. The .NET runtime documentation on managed arrays makes this clear: arrays are reference types with an immutable rank and length after creation.

The mental model that helps: think of an array as a pre-cut shelf. You can fill the slots, but you cannot add a new slot. If you need more room, you build a new, longer shelf and move everything over.

Know the Difference Between Array.Resize, List\<T\>.Add, Append, and Concat

Array.Resize Is a Copy, Not a Mutation

`Array.Resize` is a static method on the `Array` class. It allocates a new array of the requested size, copies the contents of the old array into it, and updates the reference you passed in via `ref`. The old array is abandoned. According to the Array.Resize API documentation, this is exactly what happens — there is no in-place growth.

For a single, occasional resize — expanding a configuration buffer at startup, for instance — this is perfectly acceptable. The problem is when developers put it inside a loop. Each iteration allocates a new array, copies N elements, and throws away the old one. For N appends, you copy 1 + 2 + 3 + ... + N elements, which is O(N²) total work. That's not a theoretical concern; it shows up in profilers.

List\<T\>.Add Is Built for Repeated Growth

`List<T>` solves the Array.Resize vs List\<T\> problem by managing an internal array and a separate capacity counter. When you call `.Add()` and the internal array is full, `List<T>` allocates a new backing array — typically double the current capacity — copies the existing elements, and then adds your new element. Because the capacity doubles each time, the total number of copy operations across N appends is amortized O(1) per append, or O(N) total. That's the same asymptotic cost as building the collection once.

From the caller's perspective, you just call `.Add()`. The resizing is invisible, and you don't pay a copy cost on every single call — only on the relatively rare occasions when the backing array runs out of room.

Append and Concat Do Not Change the Original Array

The LINQ methods `Enumerable.Append` and `Enumerable.Concat` are a separate category entirely. They don't modify any array. They return a new `IEnumerable<T>` that, when iterated, yields the original elements followed by the new ones. No copying happens at the point of the call — it's deferred until enumeration.

This sounds efficient, but it comes with its own cost: each call to `Append` wraps the sequence in another iterator object. Chain enough of them together and you're building a linked list of enumerators that the GC has to clean up. They're useful for composing queries, not for building collections incrementally. Treat them as query operators, not as append operations in the traditional sense. The LINQ documentation on Append is explicit that this creates a new sequence — it does not modify the source.

The Performance Cost Is Not Subtle Once You Do It More Than Once

Repeated Copying Turns Into O(N²) Pain Fast

One `Array.Resize` call costs O(N) where N is the current array length — you copy every existing element. If you're resizing to add one element at a time across a loop of M iterations, you pay 1 + 2 + 3 + ... + M copy operations, which is O(M²) total. For M = 1,000, that's roughly 500,000 element copies. For M = 10,000, it's 50 million. The curve is steep and it shows up fast in practice.

This is the trap: the code looks innocent. A single `Array.Resize` inside a loop is easy to miss in a code review if you're not thinking about C# array performance explicitly. But the profiler doesn't miss it.

What This Looks Like in Practice

A simple BenchmarkDotNet comparison between repeated `Array.Resize` and `List<T>.Add` on 10,000 elements shows a gap that's hard to argue with. The `Array.Resize` approach typically allocates an order of magnitude more memory and runs significantly slower — not because `Array.Resize` is poorly implemented, but because the algorithm is fundamentally worse. The `List<T>` approach allocates a handful of backing arrays (each double the previous size), while the resize-per-append approach allocates 10,000 separate arrays.

BenchmarkDotNet is the standard tool for this kind of measurement in .NET. If you're preparing for a performance-focused interview, running this benchmark yourself and being able to describe the results is far more convincing than citing big-O notation from memory.

Why Allocation Pressure Matters Too

The CPU time is only part of the story. Every abandoned array becomes garbage that the .NET garbage collector has to reclaim. In a short-lived process, this might not matter. In a long-running service — a web API, a background worker, a game loop — repeated short-lived large allocations create pressure on the GC's ephemeral generations. When those generations fill up, the GC pauses the process to collect. Those pauses are visible as latency spikes.

Microsoft's performance guidance for .NET explicitly flags allocation rate as a primary lever for GC pressure. Reducing allocations in hot paths isn't premature optimization — it's standard practice for services that need consistent latency.

Give the Answer Out Loud Before You Reach for Code

The 30-Second Interview Answer

Here is the answer, ready to say out loud:

"Arrays in C# are fixed-size — once you allocate one, the length doesn't change. If you need to append, you have two real options. `Array.Resize` allocates a new array and copies the old contents over, which is fine for a one-time change but expensive if you're doing it repeatedly because you're copying the whole array each time. For frequent appends, `List<T>` is the right choice — it manages its own capacity internally and grows by doubling, so repeated adds stay efficient. The tradeoff is that `List<T>` uses more memory than a tightly-sized array, but in most cases that's the right trade."

That's it. No jargon, no hand-waving, no bluffing. It names the constraint, names the two main approaches, and explains the tradeoff in terms the interviewer can evaluate.

What This Looks Like in Practice

When the interviewer pushes deeper — "what's the actual cost of `Array.Resize` in a loop?" — you go one level further:

"Each resize copies all existing elements to the new array, so if you're adding one element at a time, you're copying 1 element, then 2, then 3, all the way up to N. That's O(N²) total copy work. `List<T>` avoids that by doubling capacity when it runs out of room — so across N appends, the total copy work is O(N), which is the same as building the array once."

The goal isn't to recite a textbook. It's to demonstrate that you've thought about what actually happens in memory, not just what the API surface looks like.

Use Array.Resize Only When the Resize Is Rare, Not Part of the Loop

When Array.Resize Is Actually Fine

`Array.Resize` is not a bad API. It's a bad answer to the wrong problem. When you need to adjust an array's size once — or a small, bounded number of times — and the code is not on a hot path, it's perfectly reasonable. Loading a configuration array and appending one extra entry at startup? Fine. Adjusting a buffer size once after reading a header? Fine. The cost is O(N) and it happens once, which is indistinguishable from any other single allocation.

The Array.Resize vs List\<T\> question is really a question about frequency. One resize is a copy. A hundred resizes in a loop is a performance problem.

What This Looks Like in Practice

This is a single resize on a small array at startup. The cost is negligible. Contrast that with:

Every iteration copies the entire array. For a dataset of 10,000 items, this is 50 million copy operations. Use `List<int>` and call `.ToArray()` at the end if you need an array for the caller.

When It Is the Wrong Choice

Draw the line here: if the resize happens inside any loop, in a method called frequently, or in a system where latency or memory matter, `Array.Resize` is the wrong tool. The correct alternatives are `List<T>` for general-purpose growth, or preallocation when you know the final size upfront.

Answer the Follow-Up About Capacity Before They Ask It Twice

How List\<T\> Grows Under the Hood

`List<T>` maintains an internal array and a count of how many slots are actually used. When you call `.Add()` and the count equals the internal array's length, `List<T>` allocates a new backing array — typically double the current capacity — copies the existing elements, and then inserts the new one. The List\<T\> source and documentation confirm this doubling strategy, which gives amortized O(1) per add.

The key insight is that the doubling means the number of resize events is logarithmic in the number of elements. Adding 1,000 elements triggers roughly 10 resizes, not 1,000. The total copy work across all those resizes is O(N).

What This Looks Like in Practice

When you already know the final count, use the capacity constructor:

This pre-sizes the backing array so no resizing happens during the fill. It's a small optimization that matters in tight loops. When you don't know the count, let `List<T>` grow naturally — the doubling strategy handles it efficiently without any tuning.

In a debugger or profiler, you can inspect `list.Capacity` versus `list.Count` to see how much headroom the list has reserved. List\<T\> capacity growth is visible this way, which is useful when you're diagnosing unexpected memory usage in a long-running process.

Say Something Different When the System Cannot Afford Dynamic Allocation

Embedded Code Has Different Constraints

Not every C# codebase runs on a server with a managed GC and gigabytes of RAM. C# is used in Unity game development, in IoT firmware via .NET nanoFramework, and in real-time systems where heap allocation is either expensive or explicitly prohibited. In those contexts, "just use `List<T>`" is the wrong answer — not because `List<T>` is poorly designed, but because dynamic allocation itself is the problem.

If an interviewer asks about appending to an array in a constrained environment, the answer changes fundamentally.

What This Looks Like in Practice

In allocation-sensitive code, the standard patterns are:

  • Preallocate at startup: allocate the maximum possible size once, track a count manually, and treat the array as a bounded buffer. No resizing, no GC pressure.
  • Use `Span<T>` and stack allocation: for small, short-lived buffers, `stackalloc` puts the memory on the stack and the GC never sees it.
  • Use `ArrayPool<T>`: rent a buffer from a shared pool, use it, return it. The allocation happens once at pool initialization, not on every call.

This pattern avoids the heap entirely for buffers that fit in a few hundred bytes.

What to Say Instead of Hand-Waving

The interview-safe framing for constrained systems: "In environments where heap allocation is undesirable — real-time systems, game hot paths, embedded targets — I'd preallocate a fixed-size buffer at startup and track the count separately. If the maximum size is known, that's often the right design anyway. If I need more flexibility without heap allocation, `Span<T>` with `stackalloc` works for small buffers, and `ArrayPool<T>` works for larger ones that need to be reused across calls." That answer demonstrates you know the tradeoff exists and have tools beyond `List<T>`.

How Verve AI Can Help You Prepare for Your Interview With C# Array Performance

The structural problem with technical interview prep is that you can read the documentation, understand the concepts, and still give a rambling answer under live pressure. Knowing that `Array.Resize` copies the array is different from being able to explain it clearly when someone is watching you think. That gap — between knowledge and live delivery — is what practice is actually for.

Verve AI Interview Copilot is built to close that gap. It listens in real-time to what you're actually saying, not a canned version of what you planned to say, and responds to the real conversation as it unfolds. If you nail the first answer but stumble when the interviewer pushes on GC pressure or amortized cost, Verve AI Interview Copilot is there to help you recover — not with a script, but with a response to the actual follow-up. The copilot stays invisible during the session, so you can practice under realistic conditions without breaking the flow of the conversation. For a topic like C# array performance — where the first answer is easy and the follow-ups are where candidates separate themselves — Verve AI Interview Copilot gives you the reps that actually build fluency.

FAQ

Q: How do you correctly explain in an interview that C# arrays cannot be appended to in place?

State the structural constraint first: arrays in C# have a fixed length set at allocation, and there is no method that extends the block in memory. Any "append" requires either allocating a new, larger array or using a different data structure. Then name the consequence: `Array.Resize` does exactly that — it creates a new array and copies the old contents — which is why it's not appropriate for repeated appends.

Q: What is the performance cost of resizing an array repeatedly versus using List\<T\>.Add()?

Repeated `Array.Resize` in a loop produces O(N²) total copy work — each resize copies all existing elements, and you do that N times. `List<T>.Add()` uses a doubling strategy that makes the total copy work O(N) across all appends, amortized to O(1) per add. For large collections, the difference is dramatic: 10,000 appends via `Array.Resize` involves roughly 50 million element copies; via `List<T>`, it's closer to 20,000.

Q: When is Array.Resize acceptable, and when should you avoid it?

`Array.Resize` is acceptable when the resize happens once or a small, bounded number of times outside any hot path — startup configuration, one-time buffer adjustment, test setup. It's the wrong choice when the resize is inside a loop, called frequently, or in a system where latency or allocation pressure matters. In those cases, `List<T>` or preallocation is the correct answer.

Q: How does List\<T\> grow internally, and why does that matter for performance?

When `List<T>` runs out of capacity, it allocates a new backing array at double the current capacity and copies existing elements over. Because the capacity doubles each time, the number of resize events across N appends is logarithmic, and the total copy work is O(N). This makes the per-add cost amortized O(1), which is why `List<T>` handles repeated growth efficiently without caller involvement.

Q: What should an embedded developer do when dynamic allocation is undesirable?

Preallocate a fixed-size buffer at startup and track a count manually. For small buffers, use `stackalloc` with `Span<T>` to keep the allocation on the stack entirely. For larger reusable buffers, `ArrayPool<T>` provides a shared pool that avoids per-call heap allocation. The key principle is to move all allocation to initialization time and eliminate it from the hot path.

Q: How do LINQ Append and Concat differ from true in-place mutation?

`Enumerable.Append` and `Enumerable.Concat` return new `IEnumerable<T>` sequences — they don't modify the original array or allocate a new one at the call site. The work is deferred until enumeration. This makes them useful for composing queries but inappropriate for building collections incrementally, since chaining multiple calls creates nested iterator objects that add GC pressure and don't produce a concrete collection until you materialize them.

Q: What is the best concise answer a candidate can give when asked to append to an array in C#?

"Arrays in C# are fixed-size, so you can't append in place. `Array.Resize` creates a new array and copies the old contents — fine once, expensive in a loop. For repeated appends, `List<T>` is the right choice because it manages capacity growth internally and keeps the amortized cost per add at O(1)." That's the complete answer. If the interviewer wants more, you go into the doubling strategy, GC pressure, or constrained-environment alternatives.

Conclusion

You can now walk into that interview and answer the question on two levels: what the code does, and why it matters. Arrays are fixed-size — that's not a workaround problem, it's a design fact. `Array.Resize` copies, which is fine once and expensive repeatedly. `List<T>` is the right tool for frequent appends because it manages growth without making the caller pay a full copy on every add. And when the system can't afford dynamic allocation at all, you have `stackalloc`, `Span<T>`, and `ArrayPool<T>` as alternatives that show you've thought beyond the happy path.

The 30-second answer is in this guide. Practice saying it out loud — not to a mirror, but in a mock interview where someone can actually push back. That's the only way to know whether you've understood it or just memorized it.

CW

Cameron Wu

Interview Guidance

Ace your live interviews with AI support!

Get Started For Free

Available on Mac, Windows and iPhone