← Home
.NETPlatform and runtimeReported by candidates

How is C# code compiled and run in .NET? Explain the CLR, IL and the JIT, and the difference between managed and unmanaged code.

20 reports in the question index (8 linked below), first technical rounds: Infosys (5, two of them in Aug–Sep 2026 for 3+ and 4+ YOE), Wipro (2, "what runs the program?"), Deloitte (2), IntraEdge, Accenture and others.

Say this first · about 30 seconds
Make it yours · UDC Finance direct-debit files, signed by a native library (invented example)
The code, simplified

Invented to show the shape. Tap Next to follow one file being signed: into native code and back.

UDC.sln · where these 2 files sit
  • Debits/
Diagram · step 1The job opens a signer. The library returns a handle; the CLR wraps it in a SignerHandle.
DebitFileSigner.cs · line 3 · 1 of 4using: when the method ends, normal or by error, h is freed. .

Fix later in your repo Example written for this card: invented, not from a real codebase.

Say it out loud, then check

Show the 30-second answer
  • C# is compiled twice. First, at build time, the C# compiler, Roslyn, turns my code into IL, intermediate language, which is the same on every kind of processor. That goes into an assembly, the DLL file.
  • Then at run time the CLR, the Common Language Runtime, loads that assembly. The first time a method is called, the JIT, the just-in-time compiler, turns its IL into machine code for the exact CPU the app is running on, and caches it, so later calls run that native code directly.
  • Modern .NET does this in tiers: a quick, lightly optimised compile first so the app starts fast, then a fully optimised one in the background for methods that are called a lot. If start-up time matters, ReadyToRun puts precompiled code in the DLL, and Native AOT compiles the whole app ahead of time with no JIT at all.
  • Managed code is code the CLR runs and looks after: it checks types, frees memory with the garbage collector and turns faults into exceptions. Unmanaged code is native code outside the CLR, like a C library or a Windows API. We call it through P Invoke, which is how C# calls into a native library, and the garbage collector can't free its memory or handles, so we release them ourselves with Dispose, or a SafeHandle, a wrapper class that frees a native handle for us.
  • Where it mattered for us: after every deploy, our Navistar quote API was slow for a couple of minutes because about fourteen hundred methods were being JIT compiled on every fresh instance. Deploying to a , a second copy of the app, and sending it a warm-up request before real users arrive, plus ReadyToRun, fixed it.
Check your answer: tick what you said

You said 0 of 8

Then add your example: UDC Finance direct-debit files, signed by a native library (invented example)
  • Situation: at UDC Finance in New Zealand, each night we send the bank a direct-debit file: which customers to charge for their instalments. The bank requires each file to be signed with its own library, written in C, not .NET. That is unmanaged code: the CLR doesn't run it and the garbage collector can't see the memory it uses.
  • What I did: I called it from our .NET 8 batch job with P/Invoke (Platform Invoke: a C# method marked [DllImport] that jumps into a native library). The library hands back a handle (a number that stands for memory it holds). I wrapped that handle in a , so it is always freed, even when signing throws.
  • Result: the earlier version freed the handle only on success. Every failed signature leaked native memory, and on a bad night the job's memory crept up until restarted it. With the memory stays flat.
  • What I'd say: managed code gets the GC for free; anything native you open, you close. A makes that happen even on an exception.

See it

1/8Before .NET: C or C++ compiled straight to one CPU's machine code, for one OS.

In plain words

The senior answer · about 2 minutes
What happens, in order
  • At build time, Roslyn, the C# compiler, turns my code into IL, which is like assembly language for an imaginary CPU, and writes it into the DLL with metadata: every class, method and field, so the runtime and tools know exactly what is inside.
  • At run time the CLR loads the DLL. A method doesn't become machine code until its first call: the JIT compiles it right then, for this exact processor, x64 (Intel and AMD chips) or Arm64, and keeps the result in memory. Every later call jumps straight to that native code.
Why this design
  • One build runs on any CPU and OS that has the runtime, which is why the same DLL runs on our Windows laptops and on Linux . And because the CLR sees all the code through IL and metadata, it can check types, run the garbage collector and turn a bad memory access into a clean exception instead of a crash.
  • The cost is start-up. On a fresh process, every method pays its JIT once. Tiered compilation keeps that cheap with a quick first compile and re-optimises the hot methods later, and since .NET 8, Dynamic PGO uses what the app actually did to optimise them better.
What I do about it in production
  • Cold starts show up after deploys and scale-outs. We deploy to a and let send warm-up requests before the swap, so the JIT cost is paid before customers arrive. For start-up-sensitive services we publish with ReadyToRun; Native AOT only where reflection-heavy libraries aren't needed, because it has no JIT to fall back on.
  • I measure, not guess: shows the methods-jitted count and the IL bytes jitted. That is how we proved the post-deploy slowness was JIT; it is the production incident further down.
Managed versus unmanaged
  • Everything the CLR runs is managed: memory is freed by the GC, array bounds are checked, types are verified. Unmanaged code, a C library or a Windows API, runs outside that safety net.
  • When I call native code with P/Invoke, anything it allocates is my job. I wrap native handles in a and dispose them with using, because a forgotten handle is a leak the GC will never see. The bank signing library at UDC is my example.

One request, cold and warm

The Navistar quote API right after a deploy, then a minute later. Each step names the runtime piece at work.

1/5Cold. The first quote request after a deploy reaches a brand-new process.

The story

  1. Before

    Navistar Financial's pricing engine used to be a C++ program, compiled straight to Windows x64 machine code (the numbers a processor actually runs). It was fast. But it ran only on that one kind of computer, and C++ makes you free every piece of memory yourself. The fix, below, rewrites it in C#: the build makes a DLL (the file of compiled code your build produces), and the CLR (the .NET engine that runs it) guards its memory.

    1/4Before: C++ compiled straight to machine code for Windows on x64 chips.

  2. Why it had to change

    Compiling straight to machine code ties a program to one kind of processor and gives it no safety net. .NET puts a middle step in: a neutral code that a runtime turns into machine code on the spot, and watches while it runs.

  3. How it works now

    That neutral code is IL, stored with metadata (a list of the types and methods inside), and the runtime is the CLR. Roslyn is the C# compiler that writes the IL; the JIT is the part of the CLR that turns IL into machine code. Here is one method's life, from build to its second call:

    1/5Build: Roslyn compiles each C# method into IL and writes it into the DLL.

In the lending API

The C# you write. Deep shows the IL the compiler turns it into.

Only the key lines are shown; Deep walks the whole file.

Lending.sln · where this file sits
  • src/Domain/
⋯ 1 line in Deep
QuoteMath.cs · line 3 · 1 of 2Months still to pay on a contract: 36-month term, 10 paid, 26 left. You write C#; the build turns it into IL.

The code, file by file

One tiny method from the lending API, the IL Roslyn makes from it, and a caller that times the JIT. Tap Next to walk the diagram with the code.

Lending.sln · where these 3 files sit
  • src/
    • Domain/
    • Api/
Diagram · step 3Fix, step 1: Roslyn compiles C# to IL plus metadata, into a DLL. Same file for every CPU.
QuoteMath.cs · line 3 · 1 of 2Months still to pay on a contract: 36-month term, 10 paid, 26 left. You write C#; the build turns it into IL.

What they ask next

The follow-ups interviews really ask. Answer out loud, then tap to check.

Follow-up tree

Answer, then open the next level. That is the order an interviewer digs in.

Reporteda candidate was asked Intervieweran interviewer asks it

Production bugs, and how to tell them

Each one: what you see, why, the fix in code, and the line to say.

Production incident: Every deploy made truck quotes time out for two minutes

P3 · minorNavistar Financialcame with a deployQuote API (.NET 8, Linux App Service, 3 instances) behind the Navistar dealer portal

Composed, not a real event at Navistar Financial. The mechanism is real: a new .NET process JIT-compiles each method on its first call, so the first requests after a restart are slow; App Service can warm a staging slot before a swap, and ReadyToRun cuts JIT work at start-up (Microsoft Learn; JIT counts checked with `JitInfo` on .NET 10). Times, counts, names, ticket and PR numbers are composed.

The story in 5 lines
  1. For weeks, Navistar dealers saw truck quotes fail with "try again" for about two minutes after each Tuesday release. Then everything was normal.
  2. Releases deployed straight to the production , which restarted all three instances at once. Every instance then served real requests from a brand-new process.
  3. A new .NET process compiles each method with the JIT on its first call. The first quotes paid for about 1,400 methods each: up to 9 seconds, past the portal's 8-second timeout.
  4. The fix: deploy to a , let send warm-up requests to it, then swap. The quote API is also published with ReadyToRun, so less is compiled at start-up.
  5. A dashboard now shows methods compiled and response time per instance after each release.
Who's who (4 people)
You
.NET developer on the vendor team. Measures, finds the cause, changes the deploy.
Tech lead
Vendor senior. Owns the release pipeline, reviews the change.
L2 pod
Vendor support. Watches alerts, runs log queries during releases.
Navistar dealer support lead
Takes dealer calls. Asked why quotes fail "every Tuesday evening".

Tap a step. The map lights the place where it happens.

1/8 Detect

Detect: right after a release, quote errors jump to 18% for two minutes, then stop.

Tue 3 Mar 2026, 19:02 CT (Wed 06:32 IST, India time)Navistar dealer support, then the L2 pod

  • What: dealer support reports that truck quotes "fail on Tuesday evenings". Times are US Central (CT), where Navistar's dealers work. matches the times to our weekly 19:00 release.
  • Why it hid: each burst lasted two minutes, and the error-rate alert needed 5 minutes above 5% to fire. Retries worked, so most dealers never called.
  • How we saw it: the query below in , the query language of (Azure's store for the app's requests and errors), split by instance and by minute.
App Insights: slow and failed quotes per minute
requests
| where timestamp between
    (datetime(2026-03-04 01:00)
    .. datetime(2026-03-04 01:15))
| where name == "POST /quotes"
| summarize p95 = percentile(duration, 95),
    failed = countif(success == false)
    by cloud_RoleInstance, bin(timestamp, 1m)
In theory

An alert sensitive enough to catch a short burst right after every release.

In practice

The 5-minute window hid a 2-minute problem for seven releases.

Try it yourself · 10 minutes, on your Mac
  1. dotnet new console -n JitLab. In Program.cs, call a small method twice, timing each call with and printing before and after.
  2. dotnet run -c Release: the first call is dozens of times slower and compiles one method; the second compiles none.
  3. dotnet publish -c Release -r osx-arm64 -p:PublishReadyToRun=true -o r2r, then run ./r2r/JitLab: fewer methods compiled on the first call.
You should see

The JIT runs once per method per process. A restart means paying it again, so warm up before real traffic arrives.

The senior move

"After a deploy, the JIT cost goes to whoever sends the first request. We make sure that is our warm-up ping, not a customer."

Sources Sourced: methods are JIT-compiled on first call per process; ReadyToRun precompiles code into the assembly (Microsoft Learn, "ReadyToRun deployment"); warms a before swap and WEBSITE_SWAP_WARMUP_PING_PATH sets the warm-up path (Microsoft Learn, "Set up staging environments"); System.Runtime JIT counters. JIT counts checked with JitInfo on .NET 10. Composed: Navistar's systems, dates, counts, times, names, ticket and numbers, and the (standard columns).

Traps

Traps

What should you say instead? Tap to check.

Live coding

Easy first, then harder. Set a timer and type it before you look.

Task 1

Question index: Musteasy · 10 minutes

Show that the JIT happens on a method's first call: time the first and second call of a small method and print how many methods were JIT-compiled in between.

Closest report, Accenture: how does a .NET application execute code?
Stuck? Hints
  1. Which class gives you a precise clock?
  2. Is there a .NET API that counts JIT-compiled methods?
Show a solution
Lending.sln · where this file sits
Program.cs · line 4 · 1 of 4First call: includes the JIT.
What the interviewer checks
  • around each call.
  • before and after.
  • Explains why the second call is faster and why one timed call is not a benchmark.
  • Senior extra: mentions BenchmarkDotNet for real measurements.

Task 2

Question index: Mustmedium · 15 minutes

Call a native C function from C# with P/Invoke: get the process id from the operating system's C library, and check it against .

Closest report, Deloitte: managed and unmanaged code in C#
Stuck? Hints
  1. Which attribute tells .NET a method's body lives in a native library?
  2. On Linux and macOS the C library is called libc; on Windows the same idea is in kernel32.
Show a solution
Lending.sln · where this file sits
Diagram · step 2On the call, the CLR finds libc, finds getpid and jumps into native code.
Program.cs · line 1 · 1 of 4Looks like a normal C# call; Native is the class below. The CLR does the native jump.
What the interviewer checks
  • [DllImport("libc")] on a static extern method.
  • Parameter and return types that match C (int).
  • Can say what the CLR does at the call: marshalling, converting values between .NET types and C types and that the GC doesn't track native memory.
  • Senior extra: mentions LibraryImport, the newer source-generated form.
With AI tools

Interviewers ask how you check what an assistant tells you about the runtime. Chat answers about "JIT types" still repeat the old pre-JIT / econo-JIT list, and performance claims are often guesses. I ask the assistant to write the BenchmarkDotNet test or the command, and I trust the numbers it produces on our build, not the paragraph. For P/Invoke code an assistant writes, I check the types match the C header and that every native handle is in a .

Glossary: every name, one line each →

My notes

Paste the NOTES block from your doubt chat here. The grader never reads notes; the coach reads "Still unclear".

Read moreWhere it was asked