.NET MAUI MVVM Interview Questions and Answers (2026)

·

·

MAUI interviews in 2026 go beyond “what is MVVM?” Interviewers expect you to reason about source generators, handler architecture, DI lifetimes, and real data binding edge cases. This list covers what senior developers actually ask — with full answers.

Table of Contents

MVVM Fundamentals

Q1: Explain MVVM and why it’s used in .NET MAUI specifically

Answer: MVVM separates UI (View) from UI logic (ViewModel) and data (Model). In .NET MAUI, it’s the recommended pattern because:

  • ViewModels are pure C# — no platform-specific APIs, fully unit testable
  • Data binding in XAML eliminates code-behind coupling
  • One ViewModel codebase runs identically on iOS, Android, Windows, and macOS
  • The pattern scales — feature additions don’t pollute the View layer

Q2: What is INotifyPropertyChanged and why does MAUI data binding require it?

Answer: INotifyPropertyChanged defines the PropertyChanged event. When a ViewModel property value changes, it raises this event with the property name. MAUI’s data binding engine subscribes to this event and updates the corresponding UI element.

Without it, the binding is one-time only — the UI reads the initial value and never updates. This is why all bound properties in a ViewModel must either raise PropertyChanged or use a framework that does it automatically (like CommunityToolkit.Mvvm).

Q3: What’s the difference between one-way, two-way, and one-time binding?

<!-- One-Way: ViewModel → View (default for most controls) -->
<Label Text="{Binding UserName, Mode=OneWay}" />

<!-- Two-Way: ViewModel ↔ View (default for input controls) -->
<Entry Text="{Binding SearchQuery, Mode=TwoWay}" />

<!-- One-Time: Read once at binding creation, no updates -->
<Label Text="{Binding AppVersion, Mode=OneTime}" />

Use OneTime for static data (version numbers, labels) to avoid unnecessary binding overhead. Interviewers appreciate when you mention performance implications.

Data Binding Deep Questions

Q4: What is BindingContext and how does it propagate through the visual tree?

Answer: BindingContext is the object a View binds to. It propagates down the visual tree — a child element inherits its parent’s BindingContext unless explicitly overridden.

<!-- Page BindingContext set to MainViewModel -->
<ContentPage BindingContext="{Binding Source={StaticResource Locator}, Path=MainVM}">
  <StackLayout>
    <!-- Both inherit MainViewModel as BindingContext -->
    <Label Text="{Binding UserName}" />
    <Entry Text="{Binding Email}" />
  </StackLayout>
</ContentPage>

A common interview trap: if you set BindingContext on a child element, bindings in that subtree resolve against the child’s BindingContext, not the page’s.

Q5: What causes a NullReferenceException from a binding in MAUI?

Answer: Most commonly:

  1. BindingContext is null when the binding tries to resolve — set it before InitializeComponent() or use x:DataType to catch this at compile time
  2. A nested property path ({Binding User.Address.City}) where an intermediate object is null — use null-conditional binding or ensure the intermediate objects are initialized
  3. Accessing the UI from a background thread — always marshal UI updates to the main thread

Q6: What is compiled binding (x:DataType) and why should you use it?

Answer: Compiled bindings resolve binding expressions at compile time instead of runtime reflection. They’re faster, catch typos at compile time, and enable IDE refactoring support.

<ContentPage xmlns:vm="clr-namespace:MyApp.ViewModels"
             x:DataType="vm:MainViewModel">
  <Label Text="{Binding UserName}" />
  <!-- Compile error if UserName doesn't exist on MainViewModel -->
</ContentPage>

Performance benefit: compiled bindings avoid reflection on every property access — measurable in list views with many bound items.

CommunityToolkit.Mvvm Questions

Q7: What is a source generator and how does CommunityToolkit.Mvvm use it?

Answer: C# source generators are compiler extensions that produce additional source code during compilation. CommunityToolkit.Mvvm’s generator reads your [ObservableProperty] and [RelayCommand] attributes and emits the full property/command implementations as a partial class.

The key constraint: your class must be partial for the generator to add code to it. The generated code lives in the build output — you can inspect it in Visual Studio under Analyzers.

Q8: What does [ObservableProperty] generate, exactly?

// What you write:
[ObservableProperty]
private string _userName = string.Empty;

// What gets generated (simplified):
public string UserName
{
    get => _userName;
    set
    {
        if (!EqualityComparer<string>.Default.Equals(_userName, value))
        {
            OnUserNameChanging(value);
            OnPropertyChanging(nameof(UserName));
            _userName = value;
            OnPropertyChanged(nameof(UserName));
            OnUserNameChanged(value);
        }
    }
}

partial void OnUserNameChanging(string value);
partial void OnUserNameChanged(string value);

This answer impresses interviewers because most candidates know what the attribute does but not how.

Q9: What is [NotifyCanExecuteChangedFor] and when do you use it?

Answer: It tells the source generator to call CommandName.NotifyCanExecuteChanged() when the property changes — re-evaluating whether a command’s CanExecute returns true or false.

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SubmitCommand))]
private string _email = string.Empty;

[RelayCommand(CanExecute = nameof(CanSubmit))]
private void Submit() { ... }

private bool CanSubmit() => Email.Contains("@");

Without this, the Submit button’s enabled state would not update as the user types in the email field.

Commands and Async Patterns

Q10: What’s the difference between ICommand, RelayCommand, and AsyncRelayCommand?

  • ICommand — the interface: defines Execute, CanExecute, CanExecuteChanged
  • RelayCommand — synchronous implementation of ICommand, wraps an Action
  • AsyncRelayCommand — async implementation, wraps a Func<Task>, handles async exceptions, exposes IsRunning property

The [RelayCommand] attribute on an async Task method automatically generates an AsyncRelayCommand.

Q11: How do you prevent a command from executing multiple times concurrently?

[RelayCommand(AllowConcurrentExecutions = false)]
private async Task LoadDataAsync()
{
    // Only one execution at a time — re-tapping is ignored while running
    await _service.GetDataAsync();
}

The default for AsyncRelayCommand is to allow concurrent executions. Setting AllowConcurrentExecutions = false makes CanExecute return false while the command is running. Bind your button’s IsEnabled to LoadDataCommand.IsRunning for visual feedback.

Q12: How do you handle exceptions in async commands?

[RelayCommand]
private async Task SaveAsync()
{
    try
    {
        await _repository.SaveAsync(CurrentItem);
    }
    catch (NetworkException ex)
    {
        ErrorMessage = "Network error. Please try again.";
        _logger.LogError(ex, "Save failed");
    }
}

Don’t let exceptions surface unhandled from commands — they manifest as unobserved task exceptions that can crash the app on some platforms. Always wrap async command bodies in try/catch.

Q13: How do you navigate from a ViewModel without referencing the View?

Answer: Two clean approaches:

// Approach 1: Shell navigation (preferred)
await Shell.Current.GoToAsync("productdetail?id=42");

// Approach 2: Navigation service abstraction
public interface INavigationService
{
    Task NavigateToAsync(string route, Dictionary<string, object>? parameters = null);
}

// ViewModel uses the abstraction
public partial class ProductListViewModel : ObservableObject
{
    private readonly INavigationService _navigation;

    public ProductListViewModel(INavigationService navigation)
    {
        _navigation = navigation;
    }

    [RelayCommand]
    private async Task OpenProductAsync(Product product)
    {
        await _navigation.NavigateToAsync("productdetail",
            new Dictionary<string, object> { { "Product", product } });
    }
}

The second approach is more testable — mock INavigationService in tests to verify navigation calls without a running UI.

Q14: What is the difference between Shell.Current.GoToAsync(“route”) and Navigation.PushAsync()?

Answer: GoToAsync uses Shell’s URI routing system — it’s decoupled, supports deep linking, and doesn’t require a reference to the target page type. PushAsync requires a page instance, coupling the caller to the page class. In a Shell app, prefer GoToAsync for all navigation.

Dependency Injection and Lifetimes

Q15: What’s the difference between AddSingleton, AddTransient, and AddScoped in MAUI?

  • Singleton — one instance for the app’s lifetime. Use for services that hold shared state (auth state, app settings, cached data).
  • Transient — new instance every time resolved. Use for ViewModels — each page navigation gets a fresh ViewModel.
  • Scoped — not commonly used in MAUI (it’s tied to request scope in ASP.NET Core). Treat it as Transient in MAUI contexts.

Q16: What happens if a Transient ViewModel depends on a Singleton service that holds a database connection?

Answer: This is valid and common. The Singleton service (e.g., a repository wrapping SQLite) lives for the app’s lifetime; the Transient ViewModel is created per navigation and disposed when the page is popped. The ViewModel holds a reference to the Singleton during its lifetime — no issue. The problem would be the reverse: a Singleton holding a reference to a Transient (captive dependency), which prevents the Transient from being garbage collected.

Testing ViewModels

Q17: How do you unit test a ViewModel that uses async commands?

[Test]
public async Task LoadProducts_SetsProductsCollection()
{
    // Arrange
    var mockService = new Mock<IProductService>();
    mockService.Setup(s => s.GetAllAsync())
               .ReturnsAsync(new List<Product> { new Product { Id = 1 } });

    var vm = new ProductListViewModel(mockService.Object);

    // Act
    await vm.LoadProductsCommand.ExecuteAsync(null);

    // Assert
    Assert.That(vm.Products.Count, Is.EqualTo(1));
    Assert.That(vm.IsLoading, Is.False);
}

Q18: How do you test that a property change raises the correct event?

[Test]
public void UserName_WhenSet_RaisesPropertyChanged()
{
    var vm = new ProfileViewModel();
    var raised = new List<string?>();
    vm.PropertyChanged += (s, e) => raised.Add(e.PropertyName);

    vm.UserName = "Alice";

    Assert.That(raised, Contains.Item(nameof(vm.UserName)));
}

Advanced and Scenario-Based Questions

Q19: How would you share state between two ViewModels without making them depend on each other?

Answer: Three options, in order of preference:

  1. Shared singleton service — both ViewModels depend on the same service that holds the state (e.g., IUserSession)
  2. WeakReferenceMessenger — ViewModel A sends a message; ViewModel B subscribes and reacts
  3. Shared ObservableObject — both ViewModels hold a reference to a shared state object and bind to it

Q20: What is the MAUI handler architecture and how does it differ from Xamarin.Forms renderers?

Answer: Handlers use a Mapper dictionary that maps property names to delegate actions. When a MAUI property changes, the mapper calls the corresponding delegate that applies the change to the native view. There’s no class hierarchy to navigate and no OnElementChanged lifecycle. This makes customizations composable — you append to the mapper without subclassing. Renderers required subclassing, were tightly coupled to the platform control lifecycle, and carried significant inheritance overhead.

FAQ About MAUI Interviews

What’s the most common mistake junior MAUI developers make with MVVM?

Putting logic in code-behind. Event handlers in XAML.cs that call services directly, manipulate state, or do navigation are classic signs of incomplete MVVM understanding. Everything that isn’t strictly UI presentation should live in the ViewModel.

Do interviewers test actual coding or just theory?

Senior roles typically include a take-home or live coding exercise. Expect to build a screen with a ViewModel, data binding, and at least one async command. Use CommunityToolkit.Mvvm — it signals modern awareness of the ecosystem.

What MAUI-specific concepts are most often tested in 2026?

Source generators and CommunityToolkit.Mvvm are now standard expectations. Shell navigation (vs. NavigationPage) is frequently tested. Handler customization is asked at senior level. DI lifetime management (Singleton vs. Transient for ViewModels) is a common filter question.

Should I know Xamarin.Forms for a .NET MAUI interview?

Understanding the differences is valuable — especially for companies migrating legacy Xamarin apps. But new-project interviews focus entirely on MAUI. Don’t mention Xamarin patterns unless you’re explicitly discussing migration.

How deeply should I know data binding internals?

Know how compiled bindings work and their performance benefit. Know why INotifyPropertyChanged is required. Know the binding mode options and when to use each. Beyond that, knowing the binding engine source code internals is unnecessary for most interviews.

Conclusion

MVVM in .NET MAUI interviews reward developers who understand the why behind each pattern — not just the syntax. The candidates who stand out know that partial is required for source generators, that AsyncRelayCommand exposes IsRunning, and that Singleton-depending-on-Transient is a captive dependency anti-pattern.

Review these questions, understand the reasoning in each answer, and practice implementing a small MAUI screen from scratch using CommunityToolkit.Mvvm. That hands-on fluency is what interviews ultimately test.

[INTERNAL_LINK: MVVM in .NET MAUI with CommunityToolkit.Mvvm complete guide] [INTERNAL_LINK: .NET MAUI Shell navigation deep dive]


Leave a Reply

Your email address will not be published. Required fields are marked *