Skip to content
QSWEQB

The .NET platform and C#

One language, two job markets — modern cross-platform .NET running on Linux and in containers, and .NET Framework keeping Windows-only systems alive that still fund a great deal of the work. Find out which one a role means before you prepare for it.

High demand20 min readBackend engineer (.NET), Full-stack engineer (.NET with Angular or React), Enterprise application developer, Azure cloud engineer, Integration engineer, Legacy modernisation engineer, Game developer (Unity)Updated 2026-07-27

Assumes you know: Object-oriented fundamentals: classes, interfaces, inheritance, Comfort with a statically typed compiled language, SQL, and enough HTTP to describe a REST endpoint, Basic command line, on Windows or Linux

Overview

What this area actually covers

C# is a statically typed, garbage-collected, object-oriented language that compiles to an intermediate language executed by the CLR, the Common Language Runtime. The CLR provides just-in-time compilation, generational garbage collection, type safety and reflection. Around it sits a very large first-party library — collections, networking, cryptography, serialisation, dependency injection, logging — which is why .NET codebases tend to reach for fewer third-party packages than their JavaScript or Python equivalents.

The framework layer splits by workload. ASP.NET Core serves HTTP, from minimal APIs up through MVC and Razor Pages, with a middleware pipeline and built-in dependency injection. Entity Framework Core is the default ORM, and its translation behaviour is a reliable interview topic in its own right. Blazor runs C# in the browser or on the server for interactive UI. WPF and WinForms build Windows desktop applications and remain very much in production. MAUI targets mobile and desktop. Unity uses C# as its scripting language, which makes game development a distinct market that happens to share the language.

The critical distinction is not framework but runtime. .NET Framework is the original Windows-only implementation, at version 4.8, shipped as a component of Windows, in security-fix maintenance and receiving no new features. Modern .NET is the rebuilt cross-platform implementation — .NET Core, then .NET 5 onwards, now released every November with even-numbered versions carrying long-term support. The two share a language and much of an API surface, and are otherwise different platforms with different tooling, different deployment stories and different interview questions.

One more separation is worth making, because it explains why .NET conversations can be confusing. The language, the runtime and the libraries version independently. C# has its own version numbers and its own release notes; the runtime has others; the base class library moves with the runtime but individual packages ship on their own schedules. A team can be on a current C# compiler targeting an older runtime, or on a current runtime writing thoroughly dated C#. When someone says "we're on .NET 8", they have told you about the runtime and nothing about the code.

What people wrongly bundle in is Microsoft's product estate. Dynamics customisation, SharePoint development, Power Platform work and SQL Server administration all frequently appear alongside .NET on a job advert, and none of them is the same skill as writing C#.

The five areas underneath

The five subsections divide neatly into two language-and-runtime areas, one concurrency area, and two framework areas. That ordering is also a reasonable learning order, with one exception: if you are interviewing next week for a backend role, do async and EF Core first, because those two produce more interview questions than the other three combined.

SubsectionWhat it is for
C# LanguageThe type system, LINQ, records and nullability
Async, Tasks & ThreadingThe task model and how it deadlocks
CLR & MemoryWhere objects live and what that costs
ASP.NET CoreThe pipeline, DI, configuration and hosting
Entity Framework CoreChange tracking, translation and query performance

C# Language

The language subsection covers value types versus reference types and where each lives, generics and variance, LINQ and how a query is evaluated, records and with expressions, pattern matching, and nullable reference types. It exists separately because C# is a large language that has grown quickly, and because several of its distinctive features have no equivalent in the languages people arrive from. Value types you define yourself, real generics that keep their type arguments at runtime rather than erasing them, and expression trees that let LINQ be translated to SQL instead of merely executed are all things that make C# answers sound wrong when given in Java's semantics. Expect questions here about copying behaviour, about deferred evaluation, and about what nullable reference types do and do not promise.

Async, Tasks & Threading

The task model, async and await and the state machine the compiler generates from them, synchronisation context and the deadlocks it can cause, cancellation tokens, ValueTask, and the choice between asynchronous I/O and genuine parallelism. This is its own area because C# adopted async/await early and built it deeply into the platform, so nearly all modern .NET code is asynchronous and nearly all of the characteristic production failures are asynchrony misused. The questions are about consequences rather than syntax: what blocking on a task actually does, why async void cannot be awaited or caught, and why adding await to something did not make it faster.

CLR & Memory

Generational garbage collection, the large object heap, boxing and its cost, Span<T> and stack allocation, and diagnosing memory pressure in a running process. It is separate because it is the layer that decides whether your service is efficient, and because .NET gives you unusually fine control over allocation for a managed platform — you can define a type that lives on the stack, slice memory without copying it, and pool buffers rather than allocating them. Most application code needs none of this. Every hot path needs some of it, and senior interviews go here to find out whether you know which is which.

ASP.NET Core

The middleware pipeline and why ordering matters, dependency-injection lifetimes and the captive-dependency problem, the configuration and options system, minimal APIs versus controllers, routing, and hosting including background services. This is the framework subsection for server work, and it exists separately because ASP.NET Core is an opinionated composition of small pieces rather than a monolith: understanding it means understanding that a request passes through an ordered list of components you assembled, each of which may short-circuit it. Nearly every ASP.NET Core bug that looks mysterious is either ordering or a lifetime mismatch.

Entity Framework Core

Change tracking and the identity map, query translation and what happens when translation fails, loading strategies and the N+1 problem, migrations, and performance tuning. EF Core earns its own area for the same reason JPA does in the Java world: it is where the money goes. It is a genuinely good ORM that will do something catastrophic if you write LINQ it cannot translate, and the boundary between what runs in the database and what runs in your process is invisible on the page. You will find questions here that are really questions about SQL, asked through an abstraction.

The vocabulary you will hit on day one

This ecosystem is unusually dense with initialisms, and several of them refer to things a newcomer will otherwise conflate. The CLR is the runtime that executes your code: it loads assemblies, compiles intermediate language to machine code, enforces type safety and runs the garbage collector. The BCL, or base class library, is the first-party set of types that come with it, and its breadth is a large part of why .NET projects carry fewer dependencies than comparable projects elsewhere. IL is the intermediate language your C# compiles to, which is why a decompiler can show you readable C# from a shipped assembly and why obfuscation is a product category here.

An assembly is the unit of deployment and versioning, usually a .dll file containing IL and metadata. A NuGet package is how assemblies are distributed, and a package can contain several assemblies targeting different platforms. The target framework moniker, the string like net8.0 in a project file, declares which platform surface you are compiling against — and getting it wrong is the usual cause of a package that installs happily and then fails to load. It is worth internalising early that "which .NET do I have" has two answers: the SDK is what builds code, the runtime is what executes it, and a machine can perfectly well have one without the other.

Two release words matter for planning rather than coding. LTS releases are supported for three years and STS releases for eighteen months, which is why enterprises tend to sit on the even-numbered versions and why a role advertising an odd-numbered one is telling you something about how the team works. And AOT, ahead-of-time compilation, produces a native executable with no just-in-time step at startup, trading some reflection-dependent functionality for faster start and smaller memory use — a trade that matters for serverless and command-line tools and rarely for a long-running API.

Where it sits in a real system

.NET occupies the same architectural position as Java in the enterprise, and for similar reasons: a strong static type system, a mature runtime, first-class tooling, and a vendor a procurement department already has a contract with. A typical system is an ASP.NET Core API behind a load balancer, EF Core over SQL Server or Postgres, Redis for caching, a message broker such as Azure Service Bus or RabbitMQ, and a background worker hosted as a BackgroundService.

The part outsiders underestimate is how much of the work is integration rather than greenfield. .NET is where line-of-business systems live: order management, claims processing, billing, scheduling, regulatory reporting. So the code sits between a database that predates it, a SOAP service nobody will decommission, a file drop from a partner, and a reporting layer that reads the tables directly. Understanding transactions, idempotency and how a nightly batch interacts with a live API is more of the job than choosing a design pattern.

Where the runtime split shows up physically is deployment. Modern .NET publishes a self-contained or framework-dependent artefact that runs in a Linux container, so it sits in Kubernetes alongside everything else. .NET Framework needs Windows and, very often, IIS — which is why "we cannot containerise that service cheaply" is a sentence that shapes whole roadmaps.

.NET Framework 4.8Modern .NET (.NET 8 and later)
PlatformsWindows onlyWindows, Linux, macOS
Release modelWindows component, features frozenAnnual November release, even versions are LTS
Web stackASP.NET MVC 5, Web Forms, WCFASP.NET Core, minimal APIs, gRPC
DeploymentIIS on a Windows hostContainer, systemd service, IIS, or serverless
Typical workMaintenance, incremental modernisationNew services, cloud-native systems

How a request moves through ASP.NET Core

The single most useful mental model in this ecosystem is the middleware pipeline, because it explains a whole class of bugs that otherwise look inexplicable. A request enters a chain of components you assembled in Program.cs. Each one may inspect the request, pass it to the next, inspect the response on the way back out, or short-circuit and respond immediately. The chain is ordered, and the order is the one you wrote.

flowchart TD
    A[Request arrives] --> B[Exception handler]
    B --> C[Static files]
    C --> D[Routing]
    D --> E[Authentication]
    E --> F[Authorisation]
    F --> G[Endpoint handler]
    G --> H[Response unwinds back out]

The gap to look at is between routing and authorisation: authorisation needs to know which endpoint was selected before it can decide whether the caller may reach it, so placing it before routing produces a policy that silently never applies. Register authentication after authorisation and you get the same class of failure from the other direction.

Dependency injection is the second half of the model, and its three lifetimes are the thing to keep straight. A singleton is created once for the application. A scoped service is created once per request. A transient service is created every time it is asked for. The failure mode that gives interviewers a reliable question is the captive dependency: inject a scoped service into a singleton and the singleton captures the first request's instance and holds it forever, which for a database context means one shared, never-reset unit of work serving every request in the process.

Who does this work

Backend engineers on .NET teams build and maintain APIs and background services, and their hard days involve a slow EF Core query, a deadlock in SQL Server, or an async call that blocked a thread. Full-stack engineers pair ASP.NET Core with Angular or React — Angular is disproportionately common in this ecosystem, a consequence of both being enterprise-default choices at the same moment. Desktop engineers maintain and extend WPF and WinForms applications, work which is unglamorous, well-paid and consistently short of people. Modernisation engineers move Framework applications to modern .NET, which means untangling HttpContext, replacing WCF, and negotiating what can be left behind. Azure engineers sit half in .NET and half in infrastructure. Unity developers write C# against a different runtime profile with different constraints, particularly around allocation.

RoleWhat their week looks likeWhere they get stuck
Backend engineerEndpoints, workers, queriesQuery translation, async misuse
Full-stack engineerAPI plus an Angular or React front endContract drift between the two
Desktop engineerWPF or WinForms features and fixesThreading and UI marshalling
Modernisation engineerMoving Framework code forwardWCF, HttpContext, ambient state
Azure engineerServices plus the infrastructure under themIdentity, networking, cost

Two of those rows are consistently underserved by the candidate market and worth knowing about if you are choosing a direction. Desktop work is unfashionable and therefore uncrowded, while the applications themselves remain load-bearing inside manufacturing, healthcare and finance. Modernisation work is similar: it requires fluency in both halves of the platform, it is difficult to hire for, and the people who have done it successfully once are sought after precisely because the failure mode of a botched migration is so visible.

The people who specify this work — business analysts, product owners inside a regulated function — matter more here than in most ecosystems, because the systems encode business rules that only they can adjudicate. A .NET engineer who can read a requirements document and argue with it is unusually valuable.

Demand, adoption and how that is changing

Demand is high and unusually stable, because it is anchored in systems that generate revenue and cannot be switched off. Banks, insurers, healthcare providers, manufacturers, logistics operators, government departments and retailers all run substantial .NET estates. That produces steady hiring rather than spikes, and it survives downturns better than fashion-driven stacks.

The structural fact to understand is that the market is bifurcated, and the two halves feel completely different from the inside. New work is on modern .NET, is often containerised, and looks like any contemporary cloud backend. Maintenance and modernisation work is on .NET Framework and looks like archaeology. Both are real, both pay, and the second is larger than public discussion suggests — much of the budget in this ecosystem is spent keeping Framework systems running while a migration proceeds slowly. This is why the single most useful question in a .NET interview is one the candidate asks: which runtime is this role on, and what proportion of the work is new versus existing? An answer of "mostly .NET Framework 4.8 with a strategy deck about .NET 10" tells you exactly what your next two years look like.

The migration itself is worth understanding even if you never do one, because it explains why organisations are stuck. Moving from Framework to modern .NET is not a retarget. WCF, the old remoting-style service framework, has no direct successor in the box, so every service contract has to become something else. Web Forms has no migration path at all. HttpContext.Current, the ambient static that a great deal of older code reaches for from arbitrary depths of the call stack, does not exist, which turns an invisible dependency into an explicit parameter everywhere it was used. Anything depending on Windows-specific APIs, COM interop or the registry ties the application to Windows regardless of runtime. None of that is unsolvable and all of it is expensive, which is precisely why the second market persists.

Geographically the concentration follows enterprise IT rather than venture capital. .NET is strong wherever there are large established companies with internal development teams — much of North America outside the coastal technology hubs, the UK, the Nordics, the Netherlands, Germany, and the outsourcing and captive-centre markets in Central and Eastern Europe and India. It is comparatively thin in venture-backed startups, which default to TypeScript, Python or Go, and effectively absent from data science. If you want to work at a seed-stage company, this is not the fastest route; if you want to work in a stable industry in a mid-sized city, it is one of the best.

The language itself changes quickly, which surprises people. C# adopted async and await before most mainstream languages had anything comparable, then pattern matching, tuples, nullable reference types, records, top-level statements, primary constructors and collection expressions in successive annual releases. The practical consequence for interviews is that idiomatic C# from 2015 is visibly dated, and a candidate whose code has no pattern matching, no records and no expression-bodied members reads as someone who stopped learning.

What makes it hard

Three things, and none of them is syntax.

The first is asynchrony done properly. async and await are easy to write and easy to misuse, and the failure mode is specific: blocking on an asynchronous call with .Result or .Wait() can deadlock, because the continuation needs a context that the blocked thread is holding. Getting this right means understanding SynchronizationContext, ConfigureAwait, cancellation tokens, and why async void is almost always a bug.

The mechanism underneath is worth knowing because it makes the failures obvious rather than magical. The compiler rewrites an async method into a state machine: everything before the first await runs synchronously on the calling thread, the method then returns a task to its caller, and the remainder is registered as a continuation to run when the awaited operation completes.

stateDiagram-v2
    [*] --> Running: caller invokes
    Running --> Suspended: hits await
    Suspended --> Resumed: awaited task completes
    Resumed --> Running: next segment
    Running --> Completed: method returns
    Suspended --> Deadlocked: caller blocked on Result
    Completed --> [*]

The transition to study is the one into Deadlocked. The continuation needs somewhere to run; if the only place it may run is a thread that is sitting in .Result waiting for the very task that continuation would complete, neither side can move. ASP.NET Core removed the synchronisation context that made this common, which is why the deadlock is now mostly a legacy-Framework and desktop problem — and why an engineer who learned .NET after that change may never have met it.

The second is memory behaviour, which is where the ecosystem's depth shows. Value types versus reference types, where each actually lives, when a struct gets copied, what boxing costs, why an object over the large-object-heap threshold is treated differently, and how Span<T> lets you slice memory without allocating. You can write correct C# without any of this. You cannot make a hot path fast without it, and senior interviews go straight here.

The collector's model is simple enough to state and has real consequences. Objects are allocated in generation zero; collections there are frequent and cheap, because most objects die young and the collector only has to trace the survivors. Anything surviving is promoted, and generation two collections are much more expensive. Large objects go to a separate heap that is not compacted by default, so a workload that repeatedly allocates big arrays fragments it and grows the process even though the live set is small. This is why buffer pooling exists, why Span<T> is valuable, and why "reduce allocations" is more often the right advice in .NET than "make the code faster".

The third is EF Core's boundary. It is a good ORM that will quietly do something catastrophic if you write LINQ it cannot translate, and the difference between a query executed by the database and a query executed in your process after loading the whole table is invisible on the page. Knowing where translation stops is the skill.

Change tracking is the other half of the same subject and the half people neglect. The context keeps a copy of every entity it loaded so it can work out what changed when you save, which is convenient for a handful of entities and expensive for thousands. A read-only query that loads fifty thousand rows with tracking enabled pays for a snapshot of every one of them, and disabling tracking is a single method call that most codebases discover only after profiling.

Why study it

Study .NET if you want to build serious business systems with excellent tooling and a low tolerance for mystery. The debugger, the profiler, the IDE and the standard library are genuinely first-rate, the documentation is comprehensive, and the platform is now open source and developed in public. Study it if you want stable employment in a real industry rather than in the technology industry, or if you are already in a Microsoft-shop and want to go deeper rather than sideways.

There is a coherence argument as well. Because one vendor supplies the language, the runtime, the web framework, the ORM, the dependency-injection container, the logging abstraction and the test tooling, the pieces are designed to fit and the documentation is one estate rather than a federation. That is a genuine productivity advantage and it is the opposite trade from the JavaScript ecosystem, where you assemble your own stack from competing parts. Which trade you prefer says something real about the kind of work you will enjoy.

Do not study it if your goal is data science or machine learning; the ecosystem exists but the work is not here. Do not study it if you specifically want startup-flavoured work, because you will be swimming against the current. And do not choose it as a first language purely to learn programming — it is a fine teacher, but you will absorb a lot of framework alongside the fundamentals.

The honest correction is aimed at engineers who last evaluated this ecosystem around 2015 and concluded it was proprietary, Windows-locked and slow. All three conclusions are now wrong. The runtime is open source, runs natively on Linux, has had measurable per-release performance work for years, compiles ahead of time to a native binary when you want it to, and has a first-class command-line workflow that never requires Visual Studio. Dismissing modern .NET on a decade-old impression is one of the more common blind spots in the industry.

Your first hour

Do this from a terminal, on whatever operating system you already have, precisely because the cross-platform claim is the thing worth verifying yourself.

dotnet --list-sdks          # confirm which runtimes you have
dotnet new webapi -o Orders --no-https
cd Orders && dotnet run

Then replace the generated endpoint with something you wrote:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// The route constraint :int is enforced during routing, so a non-numeric
// id never reaches the handler and never needs validating inside it.
app.MapGet("/orders/{id:int}", (int id) => new { Id = id, Status = "open" });

app.Run();

Call it, then do three more things: add a class with a constructor dependency and register it in the container so you see how injection is wired; run dotnet publish -c Release and look at what came out; and run dotnet build 2>&1 | head after deliberately introducing a nullability warning, so you have seen what the compiler tells you.

If you have a little longer, register the same class three times over — once as a singleton, once as scoped, once as transient — have it record a new identifier in its constructor, and return that identifier from two different endpoints. Call each endpoint twice. Four numbers come back, and comparing which ones match tells you more about dependency-injection lifetimes than any amount of reading, because you will have watched a scoped service change between requests and a singleton refuse to.

The artefact is a running API you can curl, plus one sentence on what WebApplication.CreateBuilder set up on your behalf. That last part is the whole skill of working in this ecosystem — a great deal is configured for you, and competence is knowing what.

What this is not

.NET is not Windows-only, and repeating that in 2026 dates you immediately. Modern .NET runs on Linux in production at very large scale; the Windows dependency belongs to .NET Framework and to the desktop frameworks specifically.

.NET is not closed source. The runtime, the compiler, the libraries and ASP.NET Core are developed on GitHub under the .NET Foundation, and the design discussions happen in public issues you can read.

C# is not Java with different keywords. The overlap in the first week is real and then they diverge sharply: value types you define yourself, real generics with runtime type information rather than erasure, LINQ and expression trees, properties, async built into the language, ref semantics and Span<T>. Answering a C# question with Java's semantics is a specific and recognisable error.

async is not multithreading, and conflating the two is the most common misunderstanding in the ecosystem. Awaiting an I/O operation releases the current thread rather than occupying a new one — the whole point is that no thread is waiting. Making a method async does not make anything parallel, does not make it faster, and if the body is CPU-bound it merely moves the same work somewhere else. Parallelism is a separate decision with separate tools.

And "nullable reference types" are not null safety. They are a compile-time flow analysis that produces warnings, erased to attributes at runtime, and a deserialiser, a reflection call or a library compiled without the feature will happily hand you a null through a reference that claims it cannot be one. Knowing the difference between a warning and a guarantee is the mark of someone who has used the feature rather than merely enabled it.

The most valuable question in a .NET interview is the one you ask: which runtime, and how much of this is new code? The stack is the same word either way, and the two jobs are not.

Where to go next

Now practise it

10 interview questions in .NET & C#, each with the rubric the interviewer is scoring against.

All .NET questions
dotnetcsharpaspnet-coreenterpriseclr