Interview Handbook by Sayed Elmahdy العربية

Topic 25 · Engineering foundations

Operating system fundamentals

Processes vs threads, context switching, virtual memory, I/O models, scheduling.

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

16 questionsJunior · 4Mid · 7Senior · 510 quiz

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.

This entire topic is optional bonus material: useful for understanding the runtime and diagnosing production behavior, without requiring memorized kernel internals for everyday work.

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.

ProcessThreadTask
OwnerOSOS scheduler.NET abstraction
Usually separate address spaceShares process memoryNo private memory isolation
During I/OStill existsNo dedicated thread is necessarily held per async operationMay 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.

RegionContents
CodeExecutable instructions; JIT can add code pages at runtime
DataNative globals and runtime data; managed static references do not place their objects in a native data section
HeapManaged objects and native in runtime- or allocator-managed regions
Per-thread stackCall frames and execution state, with some values held in registers
Mapped regionsLibraries, 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)
🧾 Code walkthrough
  1. (1) Working set is resident process memory, including more than the managed heap and potentially shared pages.
  2. (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.

A worked example
  1. Assume one core and two runnable tasks. The lets one execute and then switches, so both progress over the same period.
  2. 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.
  3. 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.

  1. A thread runs until it blocks, yields or is preempted.
  2. The OS saves execution state such as registers and instruction position.
  3. Another thread runs; cache and locality may suffer. Switching processes can add address-space handling costs.
  4. 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.

An await can complete synchronously when its operation has already finished; not every await causes an OS context switch. Diagnose high CPU with runnable work, contention, and rather than thread count alone.

#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 .

ChoiceUse and trade-off
mutexThread ownership; named cross-process forms exist with platform-specific behavior
OS semaphorePermit counter without mutex-style ownership
lock and LockShort synchronous critical sections within a process
SemaphoreSlimIn-process coordination with WaitAsync; not a cross-process semaphore
SpinLockLow-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)
    }
}
🧾 Code walkthrough
  1. (1) Async waiting for a permit does not block a waiting thread.
  2. (2) At most four calls enter per class instance, not per cluster.
  3. (3) Release even when work fails. Cancellation during WaitAsync occurs before try, so no unacquired permit is released.
Before C# 13C# 13 onward
lock on a private objectSystem.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.

ConditionMeaningPossible break
Mutual exclusionA resource cannot be sharedSafe sharing or where possible
Hold and waitHolding one resource while waiting for anotherAcquire together or release before waiting
No preemptionA resource cannot safely be taken awayDesign release and rollback where feasible
Circular waitA cycle of dependenciesConsistent 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.

Deadlocks are not limited to blocked threads. Tasks can asynchronously wait on each other in a cycle; async/await does not eliminate dependency cycles.

#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.

  1. A program accesses a virtual address, with translation checked through the or page tables.
  2. An absent page or disallowed access triggers a page fault handled by the OS.
  3. 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.
  4. If storage must be read, the wait is more expensive; Linux commonly calls this a major fault.
  5. 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 GCServer GC
General goalClient applications or constrained resourcesServer with parallel collection
DesignLess elaborate heap organizationMultiple heaps and dedicated collection threads depending on runtime, resources and settings
DecisionMeasure , 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.

MechanismIdea
epoll on LinuxReadiness notification: a socket is ready for a read or write attempt without waiting
kqueue on BSD systems and macOSEvent notification for many resources, including sockets
on WindowsCompletion 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)
🧾 Code walkthrough
  1. (1) The operation may complete immediately or later without a waiting thread per socket.
  2. (2) For a stream socket with a nonempty buffer, zero means the peer closed its sending side after queued data was consumed.
  3. (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);
🧾 Code walkthrough
  1. (1) Create a new file and prevent conflicting sharing. Path.Combine avoids manually assembling separators.
  2. (2) Async write completion does not necessarily guarantee physical-disk durability; that is a separate contract.
  3. (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)
🧾 Code walkthrough
  1. (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. (2) ApplicationStopping is an IHostApplicationLifetime notification. Register takes a synchronous callback, not a place for async void.
  3. (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 differenceImpact on developers
Paths and caseLinux is commonly case-sensitive and Windows commonly case-insensitive; match filenames and use Path
Line endingsLF versus CRLF; configure .gitattributes and check shell scripts and shebangs
PermissionsLinux UID, GID and mode bits versus Windows ACLs; directories are not universally writable
Service managementWindows Service Control Manager versus systemd; both need identity, restart policy and logs
Native dependenciesDLLs 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

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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
Model answer

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.

Score: 0 / 0

A pending socket Task must have which dedicated resource?

A Task represents completion rather than a private thread.

Where does a struct field inside a managed class instance live?

A value type's storage depends on its context.

Which change breaks circular wait for two account locks?

A consistent resource order prevents cyclic lock acquisition.

Which statement about page faults is correct?

Zero-fill or an existing resident page may resolve a fault without storage.

What does epoll primarily report?

Readiness indicates an attempt can be made without waiting.

A service hits its open-file limit while managed heap remains small. First suspect?

Descriptors are resources independent of managed-object size.

Which signal cannot be caught for graceful cleanup?

Forced killing provides no finally or StopAsync guarantee.

Which mechanism primarily accounts for and limits Linux container resources?

Namespaces isolate views; cgroups manage resources.

What does GC heap hard limit fail to cap directly?

Process footprint extends beyond the managed heap.

Why can an app work on Windows but fail to find files on Linux?

Match case, use Path and test on the target .