Concepts
What you will learn: Connect operating-system concepts to production symptoms: a slow service, rising memory, waits or container behavior. Start with the relationship between processes, threads and Tasks, then see how memory, synchronization and I/O affect runtime behavior.
Before you start: It is enough to know that a program runs on a machine and the .NET runtime manages its objects. A process is a running program and its resources; a thread is an execution path inside it; a Task represents an operation and its result, not necessarily a new thread. These topics are optional diagnostic tools, not kernel facts to memorize.
Select a term with a dotted underline for its meaning, then continue reading from the same place.
#Optional: processes, threads and tasksBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
A executes instructions. A logical CPU is an execution lane visible to the and may be a physical core or a logical processor. The scheduler assigns execution time to runnable threads. “Runnable” means a thread can continue when scheduled, unlike one waiting for or a lock. This distinction will help explain async later.
The OS isolates programs and distributes CPU time. A process owns resources and a virtual address space. A thread is an execution path with registers and a stack; threads within a process share memory and resources, requiring synchronization. A .NET Task represents an operation and its completion rather than a new OS thread.
A process resembles an office with shared files, threads resemble employees using those files, and a Task resembles a work request. A request can await an outside message without an employee standing idle beside it.
| Process | Thread | Task | |
|---|---|---|---|
| Owner | OS | OS scheduler | .NET abstraction |
| Usually separate address space | Shares process memory | No private memory isolation | |
| During I/O | Still exists | No dedicated thread is necessarily held per async operation | May represent waiting without a dedicated thread |
CPU-bound Tasks commonly run on the ThreadPool; I/O Tasks depend on the underlying and OS. Separate processes help isolation or external execution but are not cheap substitutes for method calls.
#Optional: process memory layoutBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
Before the map: a virtual address is an address used by a program; the maps it to a physical memory page or backing storage when needed. The manages managed memory; libraries or system calls allocate native memory outside the GC's object tracking. The compiler turns into processor instructions at runtime, and changes memory addresses between runs for security. The following layout is a teaching approximation.
To understand memory growth, distinguish process regions rather than calling everything one heap. This is a teaching model; actual address ordering varies with the OS, runtime and ASLR.
| Region | Contents |
|---|---|
| Code | Executable instructions; JIT can add code pages at runtime |
| Data | Native globals and runtime data; managed static references do not place their objects in a native data section |
| Heap | Managed objects and native in runtime- or allocator-managed regions |
| Per-thread stack | Call frames and execution state, with some values held in registers |
| Mapped regions | Libraries, memory-mapped files and shared mappings |
A value type does not always live on the stack: object fields are inline, creates a managed object, and async state can live on the heap. This .NET console example needs using System.Diagnostics;; its snapshots are approximate, not a substitute for profiling.
using System.Diagnostics;
using var process = Process.GetCurrentProcess();
process.Refresh();
Console.WriteLine($"Resident bytes: {process.WorkingSet64}"); // (1)
Console.WriteLine($"Managed estimate: {GC.GetTotalMemory(false)}"); // (2)
- (1) Working set is resident process memory, including more than the managed heap and potentially shared pages.
- (2) This estimates managed allocated memory without requesting collection; it is not total process memory or reserved address space.
If managed memory stays stable while working set grows, inspect native allocations, stacks and mappings, accounting for platform-specific metric definitions.
#Optional: scheduling and context switchingBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
- Assume one core and two runnable tasks. The lets one execute and then switches, so both progress over the same period.
- A context switch saves one execution state and restores another, with overhead and possible cache effects. Concurrency here does not mean the core executes both instructions simultaneously.
- When a task waits for , another runnable task may execute. Adding threads without measurement can increase switching and memory without improving completed work.
Runnable threads commonly outnumber logical CPUs. The scheduler chooses which runs and can preempt it to let others execute. Time slices and priorities depend on the OS and scheduling class; they do not promise a fixed execution order or perfect fairness.
- A thread runs until it blocks, yields or is preempted.
- The OS saves execution state such as registers and instruction position.
- Another thread runs; cache and locality may suffer. Switching processes can add address-space handling costs.
- The original thread later resumes from its saved state rather than restarting the method.
Context switches have direct costs and indirect cache effects, but no universal fixed duration. More threads can increase contention, memory and switching instead of . Raising priority may starve other work or encounter priority inversion; it does not fix a slow query.
#Optional: mutexes, semaphores and spinlocksBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
Shared resources need admission rules, but the wrong primitive can turn an application into one queue. A has one owner; a admits a configured number of participants. A spinlock consumes while waiting instead of sleeping, so it belongs only around extremely short waits justified by measurement, never .
| Choice | Use and trade-off |
|---|---|
| mutex | Thread ownership; named cross-process forms exist with platform-specific behavior |
| OS semaphore | Permit counter without mutex-style ownership |
| lock and Lock | Short synchronous critical sections within a process |
| SemaphoreSlim | In-process coordination with WaitAsync; not a cross-process semaphore |
| SpinLock | Low-level use; wastes CPU if the owner cannot run |
This class fragment bounds concurrency. Dispose the semaphore when the service lifetime ends and no users remain.
private readonly SemaphoreSlim slots = new(4, 4);
public async Task RunLimitedAsync(Func<Task> work, CancellationToken ct)
{
await slots.WaitAsync(ct); // (1)
try
{
await work(); // (2)
}
finally
{
slots.Release(); // (3)
}
}
- (1) Async waiting for a permit does not block a waiting thread.
- (2) At most four calls enter per class instance, not per cluster.
- (3) Release even when work fails. Cancellation during WaitAsync occurs before try, so no unacquired permit is released.
| Before C# 13 | C# 13 onward |
|---|---|
| lock on a private object | System.Threading.Lock offers a dedicated synchronous-locking type |
Do not await inside lock. Choose a primitive appropriate to the context, and do not hold a thread-owned mutex across continuations that may resume on another thread.
#Optional: deadlock and the Coffman conditionsBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
A occurs when participants wait for resources held by one another and none can progress. In the classical model, all four Coffman conditions must hold; their presence alone does not prove a deadlock has occurred.
| Condition | Meaning | Possible break |
|---|---|---|
| Mutual exclusion | A resource cannot be shared | Safe sharing or where possible |
| Hold and wait | Holding one resource while waiting for another | Acquire together or release before waiting |
| No preemption | A resource cannot safely be taken away | Design release and rollback where feasible |
| Circular wait | A cycle of dependencies | Consistent resource acquisition order |
If a transfer requires two account locks, acquire them by account ID in every code path. A timeout permits failure or retry but does not automatically repair the design: release held resources and handle partial work. Starvation means work gets insufficient opportunity; livelock means participants remain active without useful progress.
#Optional: virtual memory, paging and page faultsBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
Each process sees a virtual address space. Hardware and the translate its pages to physical frames or backing storage. Reserving address space does not mean consuming that amount of . Paging supports protection, sharing and demand loading, not only compensation for insufficient RAM.
- A program accesses a virtual address, with translation checked through the or page tables.
- An absent page or disallowed access triggers a page fault handled by the OS.
- A valid demand page may be supplied through zero-fill or an existing mapping without disk ; a minor fault is not necessarily an application error.
- If storage must be read, the wait is more expensive; Linux commonly calls this a major fault.
- Invalid access may fail the process rather than load a page.
The working set or resident set represents pages currently in RAM for a process, with platform-specific definitions. Resident pages are not necessarily unique or private. Memory pressure and excessive paging can cause thrashing, where page movement displaces useful work.
Compare process and container metrics: file cache may be charged to a cgroup, and swap can be disabled or limited. Virtual-memory growth alone is not proof of a leak, and not every page fault reads a disk.
#Optional: the GC, the OS and Server GCBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
The manages managed-object lifetimes above virtual memory. The manages mappings, pages and process-level resources. Collecting an object does not necessarily reduce working set immediately: the runtime may retain capacity for reuse, and the OS controls residency.
| Workstation GC | Server GC | |
|---|---|---|
| General goal | Client applications or constrained resources | Server with parallel collection |
| Design | Less elaborate heap organization | Multiple heaps and dedicated collection threads depending on runtime, resources and settings |
| Decision | Measure , throughput and memory under actual limits; the name does not guarantee better performance | |
The web requests Server GC by default, but inspect the actual mode through GCSettings.IsServerGC. Runtime behavior can adapt to and memory limits and evolves over time; “one fixed heap per CPU” is not a universal rule.
The managed heap is only part of process footprint; native buffers, thread stacks and code also consume memory. A GC heap hard limit is not a whole-process memory cap. Leave native and accounting headroom: a container can be killed before a managed OutOfMemoryException appears. See Workstation and Server GC.
#Optional: I/O models, epoll, kqueue and IOCPBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
If every waiting socket occupies a thread, large connection counts consume stacks and scheduling capacity. Blocking suspends a thread until a result. Nonblocking I/O returns promptly when no data is available. Multiplexing monitors many sockets without one waiting thread per socket.
| Mechanism | Idea |
|---|---|
| epoll on Linux | Readiness notification: a socket is ready for a read or write attempt without waiting |
| kqueue on BSD systems and macOS | Event notification for many resources, including sockets |
| on Windows | Completion notification for supported I/O operations already initiated |
socket async hide many differences. await observes operation completion; it does not itself implement epoll. Not every async API guarantees zero blocked threads internally: file I/O, providers and behavior can differ.
This fragment belongs in an async method with using System.Net.Sockets;, a connected socket and an available ct. It performs one read, not complete protocol framing.
byte[] buffer = new byte[4096];
int read = await socket.ReceiveAsync(buffer.AsMemory(), SocketFlags.None, ct); // (1)
if (read == 0) // (2)
return;
Console.WriteLine($"Received {read} bytes"); // (3)
- (1) The operation may complete immediately or later without a waiting thread per socket.
- (2) For a stream socket with a nonempty buffer, zero means the peer closed its sending side after queued data was consumed.
- (3) A read can contain part of one message or several messages; the protocol must define boundaries.
Edge-triggered epoll requires correct nonblocking reads until EAGAIN. Ordinary applications should use .NET APIs rather than rebuild an event loop. See the epoll reference.
#Optional: file descriptors, handles and limitsBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
An open file or socket is more than a managed object: it owns an resource. On Unix, a file descriptor indexes a process resource table. Windows uses handles with access rights and platform-specific behavior. A handle is neither an ordinary pointer nor a permanent identity; values can be reused after closure.
Missing disposal can exhaust limits even with a small managed heap. Some wrappers eventually finalize resources, but timing is unpredictable and not every resource has a finalizer. Use using or await using, and appropriate SafeHandle wrappers for native interop.
This complete console example writes a file with explicit -8 and line endings for predictable cross-platform output.
using System.Text;
var path = Path.Combine(Path.GetTempPath(), $"invoice-{Guid.NewGuid():N}.txt");
await using (var file = new FileStream(path, FileMode.CreateNew,
FileAccess.Write, FileShare.None, 4096, FileOptions.Asynchronous)) // (1)
{
var bytes = Encoding.UTF8.GetBytes("Invoice total: 100 EGP\n");
await file.WriteAsync(bytes); // (2)
} // (3)
Console.WriteLine(path);
- (1) Create a new file and prevent conflicting sharing. Path.Combine avoids manually assembling separators.
- (2) Async write completion does not necessarily guarantee physical-disk durability; that is a separate contract.
- (3) DisposeAsync closes the resource even on write failure. The file itself remains rather than being deleted.
For “too many open files,” inspect descriptor counts, owners, limits and leaks before raising limits. Sockets, pipes and epoll instances also consume descriptors. Diagnostic tools may be absent from minimal containers, so plan host-side or debug-image inspection.
#Optional: signals and graceful container shutdownBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
Deployment can stop a process while it handles work. A Linux container runtime typically sends and may send after a grace period. Generic Host with ConsoleLifetime initiates shutdown on SIGTERM; SIGKILL cannot be caught and does not guarantee finally blocks. A shell wrapper can obstruct signals unless it forwards them or uses exec correctly.
This complete 10 Program.cs keeps the notification callback short. Put real async cleanup in a hosted service's StopAsync or disposal with a deadline.
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<HostOptions>(options =>
options.ShutdownTimeout = TimeSpan.FromSeconds(20)); // (1)
var app = builder.Build();
app.Lifetime.ApplicationStopping.Register(() => // (2)
app.Logger.LogInformation("Shutdown requested; draining work"));
app.MapGet("/", () => "Ready");
await app.RunAsync(); // (3)
- (1) The host deadline is one part of the plan; allow enough additional container grace for routing changes and draining. This cannot prevent forced termination.
- (2) ApplicationStopping is an IHostApplicationLifetime notification. Register takes a synchronous callback, not a place for async void.
- (3) The host manages lifecycle. Queue workers should stop accepting messages and finish or safely relinquish in-flight work.
Remove the instance from routing and make processing because crashes can occur at any time. Graceful shutdown does not replace durable queues and . See Generic Host lifecycle.
#Optional: cgroups, namespaces, Windows and LinuxBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
Linux containers use kernel mechanisms rather than a full guest per container. Namespaces alter the visible view of resources such as process IDs, networking and mounts. Cgroups group processes for resource accounting and limits such as and memory. Namespaces do not provide resource quotas, and cgroups do not provide network .
A CPU quota can cause throttling even when the host has spare capacity outside that . Memory limits can lead to OOM killing depending on conditions. Monitor cgroup usage rather than only the managed heap, and do not hardcode cgroup file paths without understanding version and mount layout. See the cgroup v2 reference.
| Practical difference | Impact on developers |
|---|---|
| Paths and case | Linux is commonly case-sensitive and Windows commonly case-insensitive; match filenames and use Path |
| Line endings | LF versus CRLF; configure .gitattributes and check shell scripts and shebangs |
| Permissions | Linux UID, GID and mode bits versus Windows ACLs; directories are not universally writable |
| Service management | Windows Service Control Manager versus systemd; both need identity, restart policy and logs |
| Native dependencies | DLLs versus shared libraries; check RID, architecture, globalization and time zones |
Do not assume every Windows filesystem has identical case rules or every Linux filesystem identical capabilities. Test on the deployment target in and avoid drive letters, registry dependencies and Windows-only libraries in cross-platform code. Containers share a kernel rather than providing isolation; reduce privileges and capabilities accordingly.
Interview questions
No results
Check your reasoning
Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?
25.1What is the difference between a process, a thread and a Task?Junior
I view a process as an operating-system resource and address-space boundary, and a thread as an execution path inside it with its own stack and registers. Threads share process memory. A Task is a representation of an operation and its completion, not a dedicated thread. tasks often run on pooled threads, while asynchronous can remain pending without one thread blocked for each operation. The distinction matters when diagnosing scaling and resource usage.
25.2What are the main regions of process memory?Junior
I think in terms of executable code, data, heaps, per-thread stacks and mapped regions such as libraries and files. In , the managed heap is only part of the process footprint; native , code and thread stacks also matter. This is a conceptual layout rather than a fixed address ordering. I avoid saying all value types live on the stack because object fields, and captured async state provide clear counterexamples.
25.3Why can too many threads reduce performance?Mid
I expect extra threads to help only when there is useful parallel work or unavoidable waiting they can overlap. Beyond that point, stacks consume memory, runnable threads compete for , and context switching harms locality. Shared locks may become more contended too. I measure and tail rather than equating thread count with capacity. For -heavy services, genuine async usually scale better than creating more threads to wait on external operations.
25.4Does every await cause a context switch?Mid
I do not assume a context switch at every await. If the awaitable has already completed, execution can continue synchronously. Otherwise the method records a continuation and returns control, and resumption depends on the scheduler and any captured context. That compiler-generated state-machine transition is not identical to an operating-system thread switch. In I also avoid assuming the continuation resumes on the original thread, since there is normally no request synchronization context requiring that.
25.5What is preemptive scheduling?Junior
I describe preemptive scheduling as the taking execution time away from a running thread so another can run. A thread does not have to voluntarily yield for that to happen. Scheduling decisions depend on priority, readiness, policy and available CPUs. I therefore protect shared state rather than assuming a short sequence cannot be interrupted. Raising a thread's priority does not make the operation atomic or guarantee a specific completion order.
25.6When would you use a mutex, semaphore or spinlock?Mid
I use a for exclusive ownership when its thread-affinity and platform semantics fit, and a to limit the number of concurrent participants. For in-process async coordination, SemaphoreSlim provides an awaitable wait without holding a blocked thread. I reserve spinlocks for specialized low-level cases where measured waits are extremely short. Spinning around wastes , and holding a thread-owned mutex across await is unsafe because the continuation can run on another thread.
25.7What are the four Coffman deadlock conditions?Mid
I remember mutual exclusion, hold and wait, no preemption, and circular wait. Together they make classical resource possible, although their presence does not prove one has occurred. I usually prevent circular wait through a consistent acquisition order and reduce hold-and-wait with smaller critical sections. Timeouts can provide a failure path, but only if code releases what it already holds and handles partial work. Async waits can also form dependency cycles.
25.8How do deadlock, starvation and livelock differ?Senior
I distinguish them by progress. In , participants cannot progress because of a dependency cycle. In starvation, some work receives insufficient access to or another resource while other work continues. In livelock, participants keep reacting or retrying but fail to make useful progress. I inspect waits, scheduling and retry behavior accordingly. Adding retries may help neither deadlock nor livelock and can make contention worse, so I first identify which progress failure is occurring.
25.9What is virtual memory?Junior
I see virtual memory as the address space presented to a process, with hardware and translation mapping it to physical memory or other backing. It supports , protection, sharing and demand loading. Reserving virtual address space does not immediately consume the same amount of . I therefore distinguish virtual size, committed and resident memory when diagnosing growth, rather than declaring a leak from one large address-space number.
25.10Does a page fault always mean a disk read or an error?Mid
I do not treat every page fault as a bug or disk read. The may be handling valid demand , zero-filling a page or attaching a page already available in memory. Other faults require storage access and can be much more expensive. An invalid or prohibited access can instead fail the operation or process. I examine fault types and memory pressure together, because a high count without context is not a complete diagnosis.
25.11Why might process memory stay high after a GC?Senior
I separate live managed objects from retained runtime capacity and total resident process memory. The may reclaim objects without immediately returning all capacity to the , and the process also owns stacks, native , mappings and code. I compare managed metrics with process and container metrics before concluding there is a managed leak. A stable heap with growing resident memory sends me toward native resources and mappings rather than repeated forced collections.
25.12Is Server GC always better for an ASP.NET Core service?Senior
I treat Server as a -oriented option rather than an automatic win. The web requests it by default, but actual behavior depends on runtime configuration and available resources. In small or densely packed containers, its memory and scheduling trade-offs may differ from a large dedicated server. I measure representative load under real and memory limits, inspect the active mode and avoid tuning GC before addressing patterns and workload bottlenecks.
25.13How do epoll and IOCP relate to .NET async I/O?Senior
I think of epoll as readiness notification on Linux and as completion notification for supported Windows . The runtime uses platform facilities behind its socket so application code can await operations without dedicating a blocked thread to each socket. I avoid generalizing that to every async API, because file I/O and provider implementations vary. The compiler's async state machine observes completion; it is not itself the operating-system event loop.
25.14Why does IDisposable matter if .NET has garbage collection?Mid
I use disposal for deterministic release of resources such as file descriptors, sockets and database connections. Garbage collection manages managed-object memory, but that does not provide timely resource cleanup. Some wrappers have finalizers as a fallback, yet waiting for them can exhaust limits before memory pressure triggers collection. I use using or await using according to the resource contract and avoid relying on process exit as the normal cleanup strategy.
25.15What happens when a Linux container receives SIGTERM?Senior
I expect the normal Generic Host with ConsoleLifetime to begin graceful shutdown when it receives . The application stops accepting or acquiring work and hosted services receive their shutdown opportunity within a deadline. I make sure signals reach the process, configure enough container grace and keep callbacks short. If the grace expires, may terminate it without cleanup, so durable state, retries and must also handle abrupt failure.
25.16What roles do namespaces and cgroups play in Linux containers?Mid
I use namespaces to explain isolated views of resources such as process IDs, networking and mounts. Cgroups provide resource grouping, accounting and controls such as and memory limits. These mechanisms complement each other but solve different problems. Containers still share the host kernel, so I also reduce privileges and capabilities. When diagnosing performance I inspect quota throttling and cgroup memory rather than assuming the application can use every resource visible on the host.
Test yourself
Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.
A pending socket Task must have which dedicated resource?
Where does a struct field inside a managed class instance live?
Which change breaks circular wait for two account locks?
Which statement about page faults is correct?
What does epoll primarily report?
A service hits its open-file limit while managed heap remains small. First suspect?
Which signal cannot be caught for graceful cleanup?
Which mechanism primarily accounts for and limits Linux container resources?
What does GC heap hard limit fail to cap directly?
Why can an app work on Windows but fail to find files on Linux?