General and Behavioral

General and behavioral interview questions: staying current, debugging war stories, technical debt, code review, and mentoring.

Staying current with .NET technologies requires a multi-faceted approach:

Official Resources:

  • Follow the official .NET Blog (devblogs.microsoft.com/dotnet) for announcements and deep dives
  • Watch Microsoft Build and .NET Conf sessions annually
  • Review release notes for each .NET version on GitHub
  • Subscribe to the .NET newsletter and ASP.NET Community Standup

Community Engagement:

  • Participate in local .NET user groups and meetups
  • Follow influential .NET developers on Twitter/X and LinkedIn (Scott Hanselman, David Fowler, etc.)
  • Read blogs from the .NET community
  • Engage in discussions on Reddit (r/dotnet, r/csharp) and Stack Overflow

Hands-On Learning:

  • Experiment with preview releases and RC versions in side projects
  • Create proof-of-concept applications with new features
  • Contribute to open-source .NET projects on GitHub
  • Complete Microsoft Learn modules and certification paths

Technical Resources:

  • Subscribe to newsletters like .NET Weekly
  • Listen to podcasts (.NET Rocks!, The .NET Core Podcast)
  • Watch YouTube channels focused on .NET development
  • Read books on new technologies and patterns

Practice:

  • Regularly refactor existing code to use newer patterns
  • Attend workshops and webinars
  • Set aside dedicated learning time each week

Related Resources

The Problem: We experienced intermittent database deadlocks in a high-traffic ASP.NET Core API that processed financial transactions. The deadlocks occurred randomly, affecting approximately 2-3% of requests during peak hours, causing transaction failures and customer complaints.

Investigation Process:

  1. Initial Analysis:

    • Reviewed application logs and found SqlException with error code 1205 (deadlock victim)
    • Enabled SQL Server deadlock tracing using trace flags 1204 and 1222
    • Captured deadlock graphs showing two transactions locking resources in opposite order
  2. Root Cause Identification:

    • Transaction A: Updated Account table, then Transaction table
    • Transaction B: Updated Transaction table, then Account table
    • Classic circular locking scenario exacerbated by long-running transactions
  3. Debugging Steps:

    • Added detailed logging with correlation IDs to track transaction flow
    • Used SQL Server Profiler to analyze query execution plans
    • Discovered N+1 query problems within transactions, extending lock duration
    • Found missing indexes on foreign key columns

Resolution:

  1. Immediate Fixes:

    • Implemented consistent locking order across all transaction types
    • Added WITH (UPDLOCK, ROWLOCK) hints to prevent lock escalation
    • Reduced transaction scope by moving non-critical operations outside transactions
  2. Long-term Improvements:

    • Enabled eager loading to eliminate N+1 queries
    • Added appropriate indexes based on execution plan analysis
    • Implemented optimistic concurrency using row versioning
    • Added retry logic with exponential backoff for transient failures
    • Set appropriate transaction isolation levels (Read Committed Snapshot)
  3. Code Example:

// Before - Problematic code
using var transaction = await context.Database.BeginTransactionAsync();
var account = await context.Accounts.FindAsync(accountId);
account.Balance += amount;
var txn = new Transaction { AccountId = accountId, Amount = amount };
context.Transactions.Add(txn);
await context.SaveChangesAsync();
await transaction.CommitAsync();

// After - Optimized code
using var transaction = await context.Database.BeginTransactionAsync();
// Consistent lock order: Account first, then Transaction
var account = await context.Accounts
    .Where(a => a.Id == accountId)
    .FirstOrDefaultAsync();
    
if (account == null)
    throw new AccountNotFoundException();

account.Balance += amount;
account.RowVersion = Guid.NewGuid(); // Optimistic concurrency

var txn = new Transaction 
{ 
    AccountId = accountId, 
    Amount = amount,
    Timestamp = DateTime.UtcNow 
};
context.Transactions.Add(txn);

await context.SaveChangesAsync();
await transaction.CommitAsync();

Results:

  • Deadlocks reduced from 2-3% to less than 0.01% of requests
  • Average response time improved by 40%
  • Customer complaints dropped to near zero

Key Learnings:

  • Always maintain consistent locking order across the application
  • Keep transactions as short as possible
  • Use appropriate isolation levels
  • Monitor and analyze deadlock graphs proactively
  • Implement proper retry mechanisms for transient failures

Technical debt is inevitable in software development, but it must be managed strategically:

1. Identification and Documentation:

  • Maintain a technical debt register in your project management tool
  • Use code comments with standardized tags (TODO, HACK, DEBT)
  • Regular code reviews to identify areas needing improvement
  • Static code analysis tools (SonarQube, Roslyn analyzers)
  • Track code metrics: cyclomatic complexity, maintainability index, code coverage

2. Categorization and Prioritization:

Classify technical debt by type:

  • Deliberate Debt: Conscious shortcuts taken to meet deadlines
  • Accidental Debt: Result of learning or outdated practices
  • Bit Rot: Code that degrades as platform/libraries evolve
  • Legacy Code: Inherited code without tests or documentation

Prioritize using a matrix:

  • High impact + High risk = Address immediately
  • High impact + Low risk = Schedule in next sprint
  • Low impact + High risk = Monitor and plan
  • Low impact + Low risk = Backlog

3. Allocation Strategy:

  • The Boy Scout Rule: Leave code better than you found it
  • 20% Time Allocation: Dedicate 20% of each sprint to technical debt
  • Debt Sprints: Quarterly sprints focused solely on technical improvements
  • Feature-Coupled Refactoring: Refactor related code when adding features

4. Practical Implementation:

// Example: Refactoring legacy code incrementally
// Step 1: Add tests to existing code (characterization tests)
[Fact]
public void LegacyOrderProcessor_CalculatesTotalCorrectly()
{
    var processor = new LegacyOrderProcessor();
    var result = processor.CalculateTotal(order);
    Assert.Equal(expectedTotal, result);
}

// Step 2: Extract and refactor in small steps
public class OrderProcessor : IOrderProcessor
{
    private readonly IDiscountCalculator _discountCalculator;
    private readonly ITaxCalculator _taxCalculator;

    // Inject dependencies for testability
    public OrderProcessor(
        IDiscountCalculator discountCalculator,
        ITaxCalculator taxCalculator)
    {
        _discountCalculator = discountCalculator;
        _taxCalculator = taxCalculator;
    }

    public decimal CalculateTotal(Order order)
    {
        var subtotal = order.Items.Sum(i => i.Price * i.Quantity);
        var discount = _discountCalculator.Calculate(order);
        var tax = _taxCalculator.Calculate(subtotal - discount);
        return subtotal - discount + tax;
    }
}

5. Prevention Strategies:

  • Establish and enforce coding standards
  • Implement automated testing requirements (minimum coverage)
  • Conduct regular architectural reviews
  • Use dependency injection and SOLID principles
  • Keep dependencies up to date
  • Document architectural decisions (ADRs)
  • Implement CI/CD pipelines with quality gates

6. Communication and Transparency:

  • Include technical debt discussions in sprint planning
  • Create visibility through dashboards and metrics
  • Educate stakeholders on the cost of technical debt
  • Frame technical debt in business terms (time to market, bug rates, productivity)

7. Metrics to Track:

  • Code coverage percentage
  • Number of open technical debt items
  • Time spent on bug fixes vs. new features
  • Deployment frequency and lead time
  • Mean time to recovery (MTTR)

Decision Framework:

When encountering technical debt, ask:

  1. Does this block current or planned features?
  2. Does this pose security or performance risks?
  3. What's the cost of fixing now vs. later?
  4. Can this be addressed incrementally?

Code Review Process:

1. Pre-Review Checklist (Author):

Before submitting a pull request:

  • Code compiles without warnings
  • All tests pass locally
  • New tests added for new functionality
  • Code coverage meets project standards (typically 80%+)
  • Self-review completed
  • PR description includes context, changes, and testing notes
  • Branch is up to date with target branch
  • Automated checks pass (CI/CD pipeline)

2. Review Structure:

I follow a tiered approach:

First Pass - High-Level Review (5-10 minutes):

  • Understand the purpose and context
  • Check if the solution aligns with requirements
  • Verify architectural patterns are followed
  • Ensure the scope is appropriate (not too large)

Second Pass - Detailed Review (15-30 minutes):

  • Line-by-line code examination
  • Check for code quality issues
  • Verify test coverage and quality
  • Look for potential bugs or edge cases

Third Pass - Final Check (5 minutes):

  • Review conversation and addressed comments
  • Ensure all concerns are resolved
  • Final approval or request changes

3. What I Look For:

Correctness:

  • Does the code do what it's supposed to do?
  • Are edge cases handled?
  • Are there potential null reference exceptions?
  • Is error handling appropriate?
// Look for proper null handling
public async Task GetUserAsync(int userId)
{
    var user = await _context.Users.FindAsync(userId);
    
    // Good - explicit null check
    if (user == null)
        throw new UserNotFoundException(userId);
    
    return _mapper.Map(user);
}

Code Quality:

  • SOLID principles adherence
  • DRY (Don't Repeat Yourself)
  • Clear and descriptive naming
  • Appropriate abstraction levels
  • Single Responsibility Principle
// Prefer specific, intention-revealing names
// Bad
public void Process(List data) { }

// Good
public void CalculateMonthlyRevenue(List orders) { }

Performance:

  • N+1 query problems
  • Unnecessary allocations
  • Inefficient algorithms
  • Proper async/await usage
  • Caching opportunities
// Check for N+1 queries
// Bad
var orders = await _context.Orders.ToListAsync();
foreach (var order in orders)
{
    // N+1: Separate query for each order
    order.Customer = await _context.Customers.FindAsync(order.CustomerId);
}

// Good
var orders = await _context.Orders
    .Include(o => o.Customer)
    .ToListAsync();

Security:

  • SQL injection prevention (parameterized queries)
  • XSS protection
  • Authentication and authorization
  • Sensitive data handling
  • Input validation
// Look for SQL injection vulnerabilities
// Bad
var sql = $"SELECT * FROM Users WHERE Username = '{username}'";

// Good
var user = await _context.Users
    .FirstOrDefaultAsync(u => u.Username == username);

Testing:

  • Unit tests for business logic
  • Integration tests for critical paths
  • Test naming and structure
  • Test coverage of edge cases
  • Proper use of mocks and stubs
[Fact]
public async Task GetUserAsync_WhenUserNotFound_ThrowsUserNotFoundException()
{
    // Arrange
    var userId = 999;
    _mockRepository
        .Setup(r => r.GetByIdAsync(userId))
        .ReturnsAsync((User)null);
    
    // Act & Assert
    await Assert.ThrowsAsync(
        () => _userService.GetUserAsync(userId));
}

Maintainability:

  • Code comments where necessary (why, not what)
  • Consistent formatting and style
  • Appropriate use of design patterns
  • Modular and loosely coupled design
  • Configuration vs. hard-coded values

API Design:

  • RESTful conventions
  • Appropriate HTTP status codes
  • Consistent response formats
  • API versioning strategy
  • Proper use of DTOs

4. Feedback Style:

  • Be respectful and constructive
  • Ask questions rather than make demands
  • Provide reasoning for suggestions
  • Acknowledge good solutions
  • Distinguish between critical issues and suggestions
// Good feedback examples:
"Consider using FirstOrDefaultAsync here instead of SingleOrDefaultAsync 
 to improve performance when we expect unique results."

"Great use of the strategy pattern here! This makes the code much more testable."

"This could throw a NullReferenceException if user.Address is null. 
 Should we add a null check or use the null-conditional operator?"

5. Review Efficiency:

  • Limit PR size (300-400 lines max)
  • Use code review checklists
  • Leverage automated tools (linters, static analysis)
  • Timely reviews (within 24 hours)
  • Avoid nitpicking on style (use automated formatters)

6. Follow-Up:

  • Verify that feedback is addressed appropriately
  • Approve when all critical issues are resolved
  • Mark minor suggestions as non-blocking
  • Document patterns for team learning

Effective mentoring is crucial for team growth and knowledge transfer. Here's my comprehensive approach:

1. Initial Assessment and Goal Setting:

First Week:

  • Conduct one-on-one to understand their background, strengths, and areas for improvement
  • Assess current skill level through pair programming sessions
  • Set SMART goals (Specific, Measurable, Achievable, Relevant, Time-bound)
  • Create a personalized development plan

Example Goals:

  • Master Entity Framework Core fundamentals in 4 weeks
  • Complete first independent feature by month 2
  • Conduct first code review by month 3
  • Present at team meeting by month 4

2. Structured Learning Path:

Foundational Phase (Weeks 1-4):

  • C# language fundamentals and .NET Core basics
  • Git workflow and version control best practices
  • Development environment setup and tools
  • Team coding standards and architecture overview

Practical Phase (Weeks 5-12):

  • Work on small, well-defined tasks with clear requirements
  • Gradually increase complexity
  • Introduce testing practices (unit, integration)
  • Code review participation

Advanced Phase (Weeks 13+):

  • Feature ownership with mentorship
  • Architecture discussions and design decisions
  • Performance optimization techniques
  • Production support and debugging

3. Hands-On Mentoring Techniques:

Pair Programming:

  • Schedule regular pair programming sessions (2-3 times per week)
  • Switch between driver and navigator roles
  • Explain thought process out loud
  • Tackle both new features and bug fixes together
// Example: Teaching LINQ and best practices during pairing
// Show the evolution of code quality

// Level 1: Basic approach
var activeUsers = new List();
foreach (var user in users)
{
    if (user.IsActive)
        activeUsers.Add(user);
}

// Level 2: LINQ query
var activeUsers = users.Where(u => u.IsActive).ToList();

// Level 3: Best practice with async
var activeUsers = await _context.Users
    .Where(u => u.IsActive)
    .AsNoTracking()
    .ToListAsync();

Code Review as Teaching Tool:

  • Review every commit they make initially
  • Provide detailed, educational feedback
  • Explain the "why" behind suggestions
  • Share resources and examples

Example Feedback:

"I noticed you're using .Result here to wait for async operations. 
This can cause deadlocks in ASP.NET applications. Use 'await' instead.

Here's why: .Result blocks the current thread, while await allows 
the thread to be released back to the thread pool.

Good resource: https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

// Instead of:
var user = GetUserAsync(id).Result;

// Use:
var user = await GetUserAsync(id);
"

4. Regular Check-ins and Feedback:

Daily:

  • Quick standup discussions
  • Available for questions via Slack/Teams
  • Review blockers immediately

Weekly:

  • 30-minute one-on-one meeting
  • Discuss progress on current tasks
  • Address challenges and concerns
  • Review learning goals

Monthly:

  • Comprehensive progress review
  • Adjust learning plan as needed
  • Celebrate achievements
  • Set new challenges

5. Knowledge Sharing:

Documentation:

  • Maintain a team wiki with common patterns
  • Create runbooks for deployment and troubleshooting
  • Document architectural decisions (ADRs)
  • Build a collection of code examples

Learning Resources:

  • Curate relevant articles, videos, and courses
  • Share Microsoft Learn paths
  • Recommend books (.NET Core in Action, Clean Code, etc.)
  • Create internal video tutorials for common tasks

Tech Talks:

  • Encourage presenting learned topics to the team
  • Organize lunch-and-learn sessions
  • Review conference talks together
  • Discuss interesting blog posts

6. Real-World Best Practices:

Gradual Task Complexity:

Week 1-2:

// Simple CRUD endpoint
[HttpGet("{id}")]
public async Task<ActionResult> GetUser(int id)
{
    var user = await _userService.GetByIdAsync(id);
    if (user == null)
        return NotFound();
    return Ok(user);
}

Week 4-6:

// Add validation, error handling, and logging
[HttpPost]
public async Task<ActionResult> CreateUser(CreateUserRequest request)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    try
    {
        var user = await _userService.CreateAsync(request);
        _logger.LogInformation("User created: {UserId}", user.Id);
        return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
    }
    catch (DuplicateUserException ex)
    {
        _logger.LogWarning(ex, "Duplicate user attempt");
        return Conflict(new { message = "User already exists" });
    }
}

Week 8-12:

// Implement complex business logic with proper architecture
public class UserService : IUserService
{
    private readonly IUserRepository _repository;
    private readonly IEmailService _emailService;
    private readonly ILogger _logger;

    public async Task CreateAsync(CreateUserRequest request)
    {
        // Validation
        await ValidateUserCreationAsync(request);

        // Map and create
        var user = _mapper.Map(request);
        user.CreatedAt = DateTime.UtcNow;

        await _repository.AddAsync(user);
        await _repository.SaveChangesAsync();

        // Side effects
        await _emailService.SendWelcomeEmailAsync(user);
        _logger.LogInformation("User created successfully: {UserId}", user.Id);

        return user;
    }
}

7. Encourage Good Habits:

Testing Mindset:

  • Write tests together for their code
  • Show test-driven development (TDD) approach
  • Explain testing pyramid and when to use different test types

Code Quality:

  • Install and configure analyzers (SonarLint, Roslyn analyzers)
  • Review compiler warnings together
  • Discuss SOLID principles with practical examples

Problem-Solving:

  • Teach debugging techniques (breakpoints, logging, profiling)
  • Show how to research issues effectively
  • Encourage asking "why" and understanding root causes

8. Building Confidence:

  • Assign them a feature they can own from start to finish
  • Let them make decisions with guidance
  • Celebrate wins publicly in team meetings
  • Provide constructive feedback privately
  • Create a safe environment for mistakes

9. Fostering Independence:

Gradual Release Model:

  1. I do, you watch: Demonstrate the task
  2. I do, you help: Work together with junior assisting
  3. You do, I help: Junior leads with mentor support
  4. You do, I watch: Junior works independently with review
  5. You do alone: Full independence with periodic check-ins

10. Measuring Success:

  • Track completion of learning goals
  • Monitor code quality metrics improvement
  • Assess increased complexity of assigned tasks
  • Gather feedback from other team members
  • Observe reduced dependency on mentorship over time

Key Principles:

  • Be patient and empathetic
  • Adapt to individual learning styles
  • Make yourself available and approachable
  • Lead by example
  • Foster a growth mindset
  • Create psychological safety

The goal is not just to teach technical skills, but to develop well-rounded engineers who can think critically, solve problems independently, and contribute positively to the team culture.