Blazor interviews at the senior level rarely stay on "what is a component." They probe whether a candidate has actually run a Blazor Web App in production: chosen a render mode under real constraints, diagnosed a circuit that leaked server memory, or shipped a WebAssembly bundle small enough that users didn't bounce before it loaded. Because one project can mix static rendering, a SignalR-backed server circuit and a WebAssembly runtime in the same page, Blazor questions also test whether a candidate reasons correctly about where code executes. The ten questions below cover render-mode trade-offs, circuit resource usage, state and prerendering pitfalls, WebAssembly performance and authentication.
Q1 Walk through the render modes a Blazor Web App supports and the trade-offs of each. How do you decide which one a given page or component should use?#
Short answer: A Blazor Web App has four render modes applied per component: static server-side rendering (the default, no interactivity), Interactive Server (runs on the server over a SignalR circuit), Interactive WebAssembly (runs in the browser) and Interactive Auto (server first, WebAssembly once it's cached). The choice is per component, not per application, so a single page can mix static content with interactive islands.
Static SSR costs nothing beyond a normal HTTP request and is what you want for content, forms and anything that should be crawlable; enhanced navigation patches the DOM on link clicks so even static pages feel like a single-page app. Interactive Server gives the fastest interactivity to build, with direct access to server resources such as a DbContext, but every event and DOM diff is a network round trip, and the server holds live state per user. Interactive WebAssembly removes the server dependency after the initial download, at the cost of a slower first load and the requirement that everything the component touches be reachable over HTTP. Interactive Auto tries to get both: Server for the first visit while WebAssembly downloads in the background, then WebAssembly on return visits, but it forces the component to work correctly under both hosts.
The decision framework a senior candidate states explicitly: default to static SSR, add Interactive Server for internal, low-latency-to-server apps, choose WebAssembly when the client must run offline or you want to shed server load, and reserve Auto for public-facing apps that need a fast first paint and are willing to support two runtimes for one component.
What interviewers look for: the render mode applied per component, not per app, stated up front; a trade-off framed in terms of latency, server cost and offline capability rather than a memorized list.
Common mistakes: treating Blazor Server and Blazor WebAssembly as competing "apps" instead of render modes inside one Blazor Web App; forgetting static SSR has no interactivity at all without a render mode.
Follow-up questions:
- What happens if an interactive component tries to host a child with a different interactive mode?
- Why must WebAssembly and Auto components live in a separate
.Clientproject? - How would you decide render mode for a dashboard with both public and authenticated widgets?
Q2 How does Interactive Server work under the hood, and what does that mean for server resource usage at scale?#
Short answer: Each browser tab using Interactive Server gets its own circuit: a stateful, server- held instance of the component tree, its scoped DI services and the SignalR connection that carries UI events and DOM diffs. Because the state lives in server memory rather than in the request, scaling Interactive Server horizontally is the same problem as scaling any other SignalR app, and it costs memory per connected user rather than per request.
A circuit isn't tied to one network connection: a client that reconnects within a grace period resumes the same circuit with its state intact, and the server releases it once that window elapses or under memory pressure. That's also why Interactive Server behind a load balancer needs sticky sessions, exactly like the SignalR scale-out story: a circuit's state lives on one server instance, and a backplane forwards messages but doesn't migrate that state. .NET 10 adds circuit state persistence, serializing a paused or long-disconnected circuit to memory or HybridCache and freeing the live circuit — trading a fixed memory cost for an unbounded one.
The practical resource question interviewers push on is what actually lives in a circuit: component fields, scoped DI service state and anything cached there. A circuit that loads a large dataset into a scoped service "for convenience" multiplies that cost by every concurrent user, which is invisible in development with one browser tab open and very visible in production under load.
What interviewers look for: circuits explained as per-connection server state, not per-request; the sticky-session or backplane requirement tied explicitly to where circuit state lives; awareness that circuit memory scales with concurrent users, not request volume.
Common mistakes: assuming a Redis backplane alone lets Interactive Server scale out without sticky sessions; caching large, per-user datasets in scoped services without accounting for the multiplier across every concurrent circuit; not load-testing with realistic concurrent-user counts, only request rate.
Follow-up questions:
- What's the difference between a circuit disconnecting gracefully and losing its connection?
- How does
.NET10's circuit pause-and-resume change the memory-versus-durability trade-off? - How would you monitor circuit count and memory in production?
Q3 What state-management pitfalls are unique to Blazor, especially across the prerendering-to-interactive handoff?#
Short answer: Interactive components are prerendered by default: the server renders static HTML first for a fast first paint, then the interactive runtime starts and the component initializes a second time. Any state loaded in OnInitializedAsync is therefore fetched twice unless you deliberately carry it across that handoff, and where "state" lives at all depends on the render mode.
@page "/movies"
@inject IMovieService MovieService
<MovieGrid Movies="Movies" />
@code {
[PersistentState] // .NET 10: survives the prerender-to-interactive handoff
public List<Movie>? Movies { get; set; }
protected override async Task OnInitializedAsync() => Movies ??= await MovieService.GetMoviesAsync();
}[PersistentState] serializes the property into the prerendered page and restores it on the interactive side instead of querying again; it must be a public property. Beyond that one handoff, state location differs by mode: component fields are always per instance, scoped services are per circuit in Interactive Server (effectively per user), and the same "scoped" service behaves like a singleton per browser tab in WebAssembly, since there's no server-side request scope to bound it. Cascading values share state down a subtree, and browser storage — plain, or through ProtectedLocalStorage/ProtectedSessionStorage for encrypted server-side access — survives reloads where in-memory state doesn't.
What interviewers look for: the double-initialization cause named precisely (prerender plus interactive start), [PersistentState] as the .NET 10 fix, and correct per-mode scoping of "scoped" services, especially the WebAssembly singleton-like behavior.
Common mistakes: not knowing why a database call fires twice on page load; assuming a scoped service behaves the same way in WebAssembly as it does in Interactive Server; storing session-critical state only in JavaScript variables, which a page reload silently discards.
Follow-up questions:
- What happens to
[PersistentState]data if the component's parameters change between prerender and interactive start? - Why is
ProtectedLocalStorageunsuitable for data a WebAssembly component must read? - How would you share state between two independently rendered islands on the same static page?
Q4 What prerendering pitfalls most often trip up teams that are new to Blazor Web Apps?#
Short answer: The two biggest are calling JavaScript before a live DOM exists and posting a static form without the metadata the framework needs to bind it back. OnAfterRenderAsync is the only lifecycle method guaranteed to run after a real DOM is present; calling JS interop from OnInitializedAsync fails during prerendering because there is nothing in the browser to call yet.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) // Never true during prerendering; safe for first-time JS interop
{
module = await JS.InvokeAsync<IJSObjectReference>("import", "./Widget.razor.js");
}
}On the forms side, a static SSR EditForm needs a unique FormName, and the bound model needs [SupplyParameterFromForm], or the posted values silently fail to bind back to the page. A related trap is passing data across the static-to-interactive boundary: parameters flowing from a static parent into an interactive child must be JSON-serializable, which rules out passing a ChildContent render fragment or a non-serializable service reference directly as a parameter. Finally, NavigationManager.NavigateTo during static rendering throws unless the app opts out, which surprises teams that expect it to behave like a normal redirect the way it does once a component is interactive.
What interviewers look for: OnAfterRenderAsync named as the only safe place for first-time JS interop, the FormName / SupplyParameterFromForm requirement for static SSR forms, and awareness of the JSON-serializable boundary between static and interactive components.
Common mistakes: debugging a "JS interop failed" error by adding retries instead of moving the call to OnAfterRenderAsync; forgetting FormName on a page with more than one form and seeing values bind to the wrong one; assuming any object can cross into an interactive child component as a parameter.
Follow-up questions:
- Why does prerendering exist at all if it complicates the lifecycle this much?
- How would you debug a form that posts but never calls
OnValidSubmit? - What changes about passing
ChildContentacross that boundary in .NET 11?
Q5 How do you meaningfully improve Blazor WebAssembly performance with AOT compilation, trimming and lazy loading? What does each one actually trade off?#
Short answer: The three techniques attack different costs. Ahead-of-time compilation trades a larger download for faster CPU-bound execution. Trimming removes unused IL to shrink that download in the first place. Lazy loading defers downloading assemblies until a route that actually needs them is visited, which improves startup time rather than raw execution speed.
Without AOT, WebAssembly runs your IL on an interpreter with a partial JIT (the Jiterpreter); setting <RunAOTCompilation>true</RunAOTCompilation> and installing the wasm-tools workload compiles to native WebAssembly at publish time, at the cost of a build that is roughly twice the size of the equivalent IL build, so it earns its keep on compute-heavy code, not typical CRUD UI. Trimming runs by default on publish and can break code the linker can't see is used, most often reflection-based serialization or dependency injection by convention, so anything trimming might remove needs an explicit root or a trimmer-safe alternative such as source-generated serialization.
<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="Contoso.Reporting.wasm" />
</ItemGroup>@inject LazyAssemblyLoader AssemblyLoader
<Router AppAssembly="@typeof(Program).Assembly" OnNavigateAsync="OnNavigateAsync">
@code {
private async Task OnNavigateAsync(NavigationContext context)
{
if (context.Path.StartsWith("reporting", StringComparison.OrdinalIgnoreCase))
{
await AssemblyLoader.LoadAssembliesAsync(["Contoso.Reporting.wasm"]);
}
}
}Marking an assembly with BlazorWebAssemblyLazyLoad stops it loading at launch, and LazyAssemblyLoader.LoadAssembliesAsync, called from the router's OnNavigateAsync, fetches it only when a matching route is visited. None of the three techniques should be applied reflexively; measure download size and time-to-interactive first, then reach for the one that addresses the real bottleneck.
What interviewers look for: the three techniques mapped to three different costs, not treated as one "make it faster" bucket; the AOT-doubles-size trade-off stated as a reason to be selective, not to avoid it entirely; lazy loading correctly framed as reducing initial download, not execution speed.
Common mistakes: enabling AOT on an app that is I/O-bound, not CPU-bound, and getting a bigger download with no measurable benefit; discovering trimming breaks reflection-based code only in production; lazy loading a route's assemblies without accounting for their transitive dependencies.
Follow-up questions:
- How would you find out whether an app's bottleneck is CPU execution or download size?
- What trimming annotations would you add to a library so it survives publish-time trimming?
- How does lazy loading interact with the Interactive Auto render mode?
Q6 How does authentication differ between Interactive Server and Interactive WebAssembly or Auto components, and what should never be trusted on the client?#
Short answer: Interactive Server authenticates the same way any ASP.NET Core app does, through cookies, OpenID Connect or another server-side scheme, with the current user available as a cascading AuthenticationState sourced directly from HttpContext.User. WebAssembly has no HttpContext, so the server serializes the authentication state into the prerendered page and the client deserializes it, and from that point on, anything the client-side code decides based on that state is a UI convenience, not a security boundary.
// Server project
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents()
.AddAuthenticationStateSerialization();
// .Client project
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthenticationStateDeserialization();AuthorizeView and [Authorize] on a component only hide or show UI; they don't stop a user from reading the compiled assembly in their own browser or calling the underlying API directly with curl. Every WebAssembly or Auto component that reaches a protected resource must call a server API that independently enforces authorization, and secrets must never ship in the client bundle. For apps calling external APIs on the user's behalf, keeping tokens server-side behind a backend-for-frontend, as covered in the API gateway and BFF interview questions, beats handling OAuth tokens directly inside WebAssembly.
What interviewers look for: the serialize-on-server, deserialize-on-client mechanism named correctly, and an unprompted, explicit statement that client-side authorization is UI-only and must be re-enforced on every server endpoint.
Common mistakes: treating AuthorizeView as a security control instead of a UI convenience; putting API keys or connection strings in WebAssembly-reachable configuration; skipping server-side authorization checks because the UI already "handles" who can see a button.
Follow-up questions:
- What happens to a WebAssembly component's view of the user if their session expires mid-visit?
- How would you keep an access token out of WebAssembly entirely for a public-facing app?
- What changed for passkey support in ASP.NET Core Identity in .NET 10?
Q7 When would you choose Blazor over a JavaScript SPA framework, and when would you actively steer a team away from it?#
Short answer: Choose Blazor when the team is already a .NET shop, the app benefits from sharing models and validation logic between client and server, and the UI is a line-of-business or internal application where a large, JavaScript-specific component ecosystem isn't the deciding factor. Steer away when the hiring pool and existing codebase are JavaScript-centric, the app needs the largest possible pool of pre-built UI components, or it must integrate deeply into an existing JavaScript SPA.
| Factor | Blazor Web App | JavaScript SPA with an API |
|---|---|---|
| Language | C# end to end | TypeScript/JavaScript client, any server language |
| Code sharing with backend | Models, validation, services | Contract only, via OpenAPI |
| Component ecosystem | Growing | Largest available |
| Initial load | Fast with static SSR | Depends on bundle size and SSR setup |
| Team fit | .NET-heavy teams | Frontend-specialized teams |
The nuance interviewers reward is refusing a one-size answer: Blazor's static SSR default and per-component interactivity make the initial-load argument against it weaker than it was a few releases ago, so the deciding factor is usually team composition and code-sharing value, not raw capability. A senior candidate also flags that "Blazor vs JavaScript" is sometimes the wrong question if the real requirement is a native mobile or desktop shell, where Blazor Hybrid reuses the same components.
What interviewers look for: a decision driven by team skills and code-sharing value rather than "Blazor is faster" or "JavaScript has more libraries" as a blanket claim; recognition that the initial- load gap has narrowed with static SSR.
Common mistakes: treating the choice as purely technical and ignoring hiring and team familiarity; dismissing Blazor based on outdated assumptions about Blazor Server's original, interactivity-only model.
Follow-up questions:
- How would this decision change for a public marketing site versus an internal admin tool?
- What does Blazor Hybrid change about this comparison for a desktop or mobile client?
- How would you de-risk a first Blazor project for a team with no prior C# UI experience?
Q8 A Blazor Server app performs fine in development but in production, circuits pile up server memory under load and users start seeing "Attempting to reconnect" banners. Diagnose it.#
Short answer: This is almost always one of three causes: per-circuit state that's too large for the concurrent user count, missing sticky sessions behind a load balancer so reconnect attempts land on a server that never held the original circuit, or expensive server-side rendering work blocking the circuit's single-threaded event queue long enough that the client times out waiting for a response.
Development masks all three because it usually runs one browser tab against one server instance. In production, every concurrent user is a circuit holding its own component tree and scoped-service state, so a service that "conveniently" caches a large dataset per circuit multiplies that cost by every active user, not by request volume. If the deployment scaled out without sticky sessions, a reconnect attempt can land on an instance that never held that circuit, which the client experiences as a permanent disconnect. And since a circuit processes one render at a time, a slow synchronous database call or an unvirtualized render of thousands of rows blocks that user's UI updates long enough to trip the client's reconnect timeout.
The fix set mirrors the causes: keep circuit-held state small and prefer paging or Virtualize over loading everything at once, configure session affinity (or move to WebAssembly/Auto for state-light, widely distributed usage), and load test with realistic concurrent-user counts, not request throughput, since that is the metric that actually predicts circuit memory pressure.
What interviewers look for: all three causes considered rather than jumping straight to "add more memory," and load testing framed around concurrent circuits, not requests per second.
Common mistakes: scaling out Interactive Server without sticky sessions and being surprised reconnects fail; treating a memory or CPU alert as a scaling problem alone rather than checking what a single circuit actually holds.
Follow-up questions:
- How would circuit state persistence change the failure mode you just described?
- What metrics would tell you a specific circuit, not the whole server, is the problem?
- When would you migrate a specific page from Interactive Server to WebAssembly to fix this?
Q9 How would you design a component that must render correctly under Interactive Auto, where it starts on the server and later hot-swaps to WebAssembly?#
Short answer: The component and everything it directly references must live in the .Client project so it's included in the WebAssembly bundle, and it cannot assume server-only resources such as a DbContext are reachable, since the same component executes with no server process behind it once it switches. The safest design calls the same HTTP API in both phases through an injected abstraction, rather than branching internally between "direct database access" and "HTTP call."
public interface ICatalogService
{
Task<IReadOnlyList<Product>> GetFeaturedAsync(CancellationToken ct);
}
// Registered once in the .Client project; calls the same public API endpoint
// whether the component is currently executing on the server or in the browser.
public sealed class HttpCatalogService(HttpClient http) : ICatalogService
{
public async Task<IReadOnlyList<Product>> GetFeaturedAsync(CancellationToken ct) =>
await http.GetFromJsonAsync<List<Product>>("api/catalog/featured", ct) ?? [];
}Parameters flowing into the component from a static or server-rendered parent must stay JSON- serializable, since they cross a real serialization boundary when the render mode changes host. Auto never swaps a component that's already on the page mid-session, so the constraint is "must work correctly under either host from a cold start," not "must survive switching hosts live." RendererInfo and, since .NET 9, AssignedRenderMode let a component branch on where it's currently running when a genuine difference is unavoidable, such as disabling a button until the interactive runtime is ready, but that should be the exception, not the default way of handling the server/WebAssembly split.
What interviewers look for: the HTTP-API-in-both-phases pattern as the default design, not "branch internally," and a correct statement of what Auto guarantees (works from cold start under either host) versus what it doesn't (a live host swap mid-session).
Common mistakes: injecting DbContext directly into a component meant to run under Auto, which breaks the moment it renders in WebAssembly; passing a non-serializable object as a parameter into an Auto component from a static parent.
Follow-up questions:
- How would you test an Auto component under both hosts in CI?
- What's the practical difference between designing for Auto and designing for WebAssembly alone?
- When would you deliberately restrict a component to Interactive Server instead of supporting Auto?
Q10 What changed about Blazor in .NET 10 that a team upgrading from .NET 8 should plan for, and what's coming in .NET 11 that affects the calculus?#
Short answer: .NET 10 focused on closing gaps between render modes rather than adding new ones: declarative [PersistentState], circuit state persistence with pause and resume, NavigationManager .NotFound() for not-found handling, source-generated form validation for nested objects and collections, and JavaScript interop helpers for constructing JS objects and reading properties directly. .NET 11, at Release Candidate 1 as of September 2026, extends forms and rendering further rather than changing the render-mode model itself.
The .NET 10 changes worth planning around are the ones that remove hand-written workarounds: teams that built their own double-load guard around prerendering can replace it with [PersistentState], and teams that hand-rolled reconnect-state caching can evaluate circuit state persistence instead. .NET 11's previews add client-side validation for static SSR forms, asynchronous validation through EditContext.ValidateAsync, a CacheView component, variable-height virtualization, and automatic circuit pausing for inactive tabs as a supported package. It also removes the .NET 10 restriction on passing ChildContent across the static-to-interactive boundary.
A senior answer treats this as ongoing platform evolution to track, not a one-time migration: Blazor's render-mode model has been stable since .NET 8, and most releases since then have been about removing friction at the edges rather than changing the fundamental architecture.
What interviewers look for: specific .NET 10 features named, not a vague "it got faster"; the .NET 11 preview features framed as continuations of .NET 10's direction, showing the candidate tracks the platform rather than having learned Blazor once and stopped.
Common mistakes: confusing circuit state persistence (.NET 10, for Interactive Server) with component prerendered-state persistence ([PersistentState], works under any interactive mode); assuming the render-mode model itself changed between .NET 8 and .NET 10, when it has stayed stable.
Follow-up questions:
- Which .NET 10 feature would most reduce code in an app that already hand-rolled its own workaround?
- How would you evaluate whether to adopt a .NET 11 preview feature before general availability?
- What would make you delay a Blazor upgrade even with backward-compatible changes available?
Quick-Fire Round#
| Question | Answer |
|---|---|
Default render mode with no @rendermode applied? | Static SSR: no interactivity at all. |
| What connects an Interactive Server component to the server? | A SignalR circuit, usually over WebSockets. |
| Where is state serialized so WebAssembly knows the current user? | Into the prerendered page, via authentication state serialization. |
| Safe place for first-time JS interop? | OnAfterRenderAsync, never OnInitializedAsync. |
What does [PersistentState] fix? | Data reloading twice across the prerender-to-interactive handoff. |
| Why does AOT roughly double WebAssembly build size? | It compiles IL to native code instead of shipping it for interpretation. |
| What must Auto and WebAssembly components live in? | The .Client project, so they're in the browser bundle. |
Does AuthorizeView enforce security? | No — it only hides UI; enforce authorization on the server. |
How to Prepare#
- Be able to name all four render modes, where each executes, and decide between them for a concrete scenario without hesitating.
- Explain circuits as per-connection server state and connect that directly to sticky sessions and backplane requirements when scaling out.
- Know exactly why prerendering causes double initialization and how
[PersistentState]fixes it. - Separate AOT, trimming and lazy loading by what each one actually costs and saves; don't treat them as one undifferentiated "make it faster" lever.
- Have a firm, unprompted answer for why client-side authorization is UI-only.
- Practice a concrete diagnosis for circuits piling up server memory under production load.