CLR & Runtime
Managed code, garbage collection, assemblies, reflection, serialization, IL/JIT, and LINQ.
11 questions
JuniorTheoryVery commonWhat is the garbage collector in .NET, and how does it manage memory?
What is the garbage collector in .NET, and how does it manage memory?
The GC (garbage collector) automatically reclaims managed-heap objects that are no longer reachable from any root (locals, statics, GC handles). It runs on its own schedule, so freeing is non-deterministic: you allocate objects but never free them manually, and you cannot predict exactly when an unreachable object's memory is reclaimed. This eliminates dangling-pointer and double-free bugs.
Common mistakes
- ✗Expecting deterministic, scope-tied release like a C++ destructor instead of non-deterministic collection
- ✗Believing .NET uses reference counting, so circular references would leak; the GC traces reachability instead
- ✗Thinking calling GC.Collect() manually is normal practice, when it usually hurts performance
Follow-up questions
- →How does the GC decide an object is unreachable rather than just temporarily unused?
- →Why is calling GC.Collect() manually usually discouraged in production code?
JuniorTheoryCommonWhat is an assembly in .NET, and what does it contain?
What is an assembly in .NET, and what does it contain?
An assembly is the fundamental unit of deployment and versioning in .NET, packaged as a .dll or .exe. It contains compiled IL code, a manifest (assembly identity, version, and referenced assemblies), type metadata describing every type and member, and optional embedded resources. The CLR loads and version-checks code at assembly granularity.
Common mistakes
- ✗Confusing an assembly with a namespace; namespaces organize names while assemblies are physical deployment units
- ✗Forgetting the manifest, thinking an assembly is just compiled IL without identity and reference metadata
- ✗Assuming one assembly equals one class or file, when an assembly can hold many types across files
Follow-up questions
- →How does a private assembly differ from a shared one in the GAC (Global Assembly Cache)?
- →How does the CLR resolve and locate a referenced assembly at load time?
JuniorTheoryCommonWhat is managed code, and how does it differ from unmanaged code?
What is managed code, and how does it differ from unmanaged code?
Managed code runs under the CLR (Common Language Runtime), which provides garbage collection, type safety, exception handling, and JIT compilation. Unmanaged code (e.g. C/C++) compiles straight to native machine code and runs directly on the OS, managing its own memory and lifetime without those runtime services.
Common mistakes
- ✗Assuming all .NET code is fully managed, forgetting that native interop runs as unmanaged code outside CLR control
- ✗Thinking managed only means garbage-collected, ignoring type safety, JIT, and exception services the CLR also provides
- ✗Believing unmanaged code is always faster, ignoring that JIT can optimize for the actual host CPU
Follow-up questions
- →How does a managed app call into an unmanaged native library safely?
- →What does the CLR (Common Language Runtime) do at the moment a managed assembly loads?
MiddleTheoryCommonHow does C# go from source to running code via IL and the JIT compiler?
How does C# go from source to running code via IL and the JIT compiler?
The C# compiler emits IL (Intermediate Language) — a CPU-independent bytecode — into the assembly along with metadata, not native code. At runtime the JIT (Just-In-Time) compiler translates each method's IL into native machine code lazily, on its first call, then caches the result so later calls run native. This lets one assembly target any CPU and optimize for the actual host.
Common mistakes
- ✗Thinking the C# compiler produces native code directly, missing the IL intermediate stage
- ✗Believing the whole assembly is JIT-compiled at startup rather than per method on first call
- ✗Confusing IL with an interpreted language re-parsed each call, instead of compiled once and cached
Follow-up questions
- →What is AOT (Ahead-Of-Time) compilation and how does it differ from JIT?
- →How can the JIT produce faster code than a fixed ahead-of-time native build?
MiddleTheoryOccasionalWhat is LINQ (Language Integrated Query), and how does it execute?
What is LINQ (Language Integrated Query), and how does it execute?
LINQ (Language Integrated Query) is a unified set of query operators and a query syntax that work over any sequence implementing IEnumerable or IQueryable — collections, databases, XML. Operators like Where, Select, and OrderBy compose into a pipeline. Most queries execute lazily (deferred): nothing runs until you enumerate the result, and each item flows through the pipeline on demand.
Common mistakes
- ✗Assuming LINQ queries run eagerly, when most use deferred execution until enumerated
- ✗Forgetting that re-enumerating a deferred query re-runs it, re-reading the possibly-changed source
- ✗Thinking LINQ only targets databases, ignoring LINQ to Objects over any in-memory sequence
Follow-up questions
- →How does
IQueryablelet a provider translate a query into SQL instead of running it in memory? - →What pitfalls does deferred execution cause when the underlying source changes between enumerations?
MiddleTheoryOccasionalWhat is reflection in .NET, and what does it let you do at runtime?
What is reflection in .NET, and what does it let you do at runtime?
Reflection inspects type metadata at runtime via System.Reflection and the Type class: you can enumerate types, members, attributes, and signatures of any loaded assembly. Beyond inspection, it can act — instantiating types, reading and setting fields and properties, and invoking methods dynamically by name. This powers serializers, dependency injection, ORMs, and test frameworks that work over types unknown at compile time.
Common mistakes
- ✗Thinking reflection is read-only, forgetting it can instantiate types and invoke members dynamically
- ✗Assuming reflection reads source files, when it reads compiled metadata embedded in the assembly
- ✗Ignoring reflection's runtime cost, calling it in hot paths instead of caching MemberInfo lookups
Follow-up questions
- →Why is reflection-based member access slower than a direct compiled call?
- →How do custom attributes combine with reflection to drive serializers and validators?
MiddleTheoryOccasionalWhat is serialization in .NET, and why is it used?
What is serialization in .NET, and why is it used?
Serialization converts an in-memory object graph into a portable form — JSON, XML, or binary — that can be stored or transmitted. Deserialization is the reverse: it reconstructs the object graph from that form. It is used to persist state to disk or a database, send objects over a network or message queue, and cache data, so the same object can cross process, machine, or time boundaries.
Common mistakes
- ✗Confusing serialization with thread serialization (serializing access), which is an unrelated concept
- ✗Forgetting deserialization can fail or run untrusted code, treating incoming payloads as always safe
- ✗Assuming all object state serializes, ignoring that fields like file handles or events do not round-trip
Follow-up questions
- →Why are binary deserialization formats considered a security risk for untrusted input?
- →How do attributes control which members are serialized and under what names?
SeniorTheoryOccasionalWhat is an expression tree, and how do LINQ providers use it?
What is an expression tree, and how do LINQ providers use it?
An expression tree (Expression<Func<...>>) represents code as an inspectable data structure — a tree of nodes for operations, parameters, and constants — rather than executable IL. Because it is data, a LINQ provider can walk and analyze it at runtime: providers like Entity Framework translate the tree into another language such as SQL, instead of running the lambda in memory. You can also compile a tree to a delegate on demand.
Common mistakes
- ✗Confusing
Expression<Func<...>>with a plainFunc<...>delegate, missing that one is data and one is executable - ✗Thinking expression trees exist only at compile time, when they are runtime objects providers inspect
- ✗Assuming any in-memory lambda translates to SQL, ignoring that only expression trees (IQueryable) do
Follow-up questions
- →Why does an
IQueryablequery use expression trees whileIEnumerableuses compiled delegates? - →What kinds of C# code cannot be represented as an expression tree, and why?
SeniorTheoryOccasionalWhat is a finalizer in C#, and when should you prefer Dispose instead?
What is a finalizer in C#, and when should you prefer Dispose instead?
A finalizer (~Type()) runs non-deterministically on a dedicated GC thread just before an object is reclaimed, with no guarantee of when or in what order. Because finalizable objects survive an extra collection cycle (placed on the finalization queue, then collected later), they delay reclamation and add GC pressure. Prefer IDisposable/Dispose for deterministic cleanup, and keep a finalizer only as a safety net for unmanaged resources.
Common mistakes
- ✗Treating a finalizer like a C++ destructor with deterministic, scope-tied timing on the calling thread
- ✗Adding finalizers broadly, not realizing they delay reclamation by an extra cycle and add GC overhead
- ✗Implementing a finalizer but forgetting GC.SuppressFinalize in Dispose, so the object still gets queued
Follow-up questions
- →How does the Dispose pattern combine a finalizer with GC.SuppressFinalize correctly?
- →Why can a finalizer resurrect an object, and what problems does that cause?
SeniorTheoryOccasionalWhy is the .NET garbage collector generational, and how do generations work?
Why is the .NET garbage collector generational, and how do generations work?
The GC partitions the managed heap into generations 0, 1, and 2. New objects start in gen 0; objects that survive a collection get promoted to the next, higher generation. This exploits the generational hypothesis — most objects die young — so the GC can collect gen 0 frequently and cheaply, touching only recently allocated memory, while expensive full gen-2 collections that scan everything run far less often.
Common mistakes
- ✗Believing generations rank objects by size rather than by how many collections they have survived
- ✗Thinking survivors stay put, missing that surviving a collection promotes an object to an older generation
- ✗Assuming full gen-2 collections are the common cheap case, when gen-0 collections are the frequent cheap ones
Follow-up questions
- →Where does the Large Object Heap fit, and why is it collected with gen 2?
- →How can excessive object promotion to gen 2 hurt application throughput?
MiddleTheoryRareWhat is a strong name for an assembly, and what does it provide?
What is a strong name for an assembly, and what does it provide?
A strong name gives an assembly a globally unique identity composed of its simple name, version, culture, and public key, plus a digital signature over the assembly. The signature lets the CLR detect tampering and bind to an exact version, and the unique identity allows the assembly to be installed into the GAC (Global Assembly Cache) for machine-wide sharing without name collisions.
Common mistakes
- ✗Thinking a strong name encrypts the assembly, when it signs for identity and integrity, not confidentiality
- ✗Confusing a strong name with just a unique file name, missing the version, public key, and signature
- ✗Assuming strong naming alone proves publisher trust, which actually requires Authenticode certificates
Follow-up questions
- →How does strong-name signing differ from Authenticode publisher certificates?
- →Why did a strong-named assembly require its dependencies to be strong-named on older frameworks?