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 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.
Subsection
What it is for
C# Language
The type system, LINQ, records and nullability
Async, Tasks & Threading
The task model and how it deadlocks
CLR & Memory
Where objects live and what that costs
ASP.NET Core
The pipeline, DI, configuration and hosting
Entity Framework Core
Change 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.8
Modern .NET (.NET 8 and later)
Platforms
Windows only
Windows, Linux, macOS
Release model
Windows component, features frozen
Annual November release, even versions are LTS
Web stack
ASP.NET MVC 5, Web Forms, WCF
ASP.NET Core, minimal APIs, gRPC
Deployment
IIS on a Windows host
Container, systemd service, IIS, or serverless
Typical work
Maintenance, incremental modernisation
New 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.
Loading diagram…
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.
Role
What their week looks like
Where they get stuck
Backend engineer
Endpoints, workers, queries
Query translation, async misuse
Full-stack engineer
API plus an Angular or React front end
Contract drift between the two
Desktop engineer
WPF or WinForms features and fixes
Threading and UI marshalling
Modernisation engineer
Moving Framework code forward
WCF, HttpContext, ambient state
Azure engineer
Services plus the infrastructure under them
Identity, 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.
Loading diagram…
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.