How to Make Slow Code Easier to Optimize

Application feels slow?

Before optimizing it, find out what is slow.

Use profiling, tracing, metrics, or benchmarks to measure where the application spends its time.

How to Make Slow Code Easier to Optimize

You might discover that a request spends:

Validation        4 ms
Business logic   12 ms
Database        680 ms
Serialization     8 ms

Optimizing 12 ms of business logic probably won't fix a 700 ms request.

Measure things like:

  • Execution time
  • Database calls
  • External requests
  • CPU usage
  • Memory allocations
  • Lock contention

Then optimize the part responsible for the problem.

Performance optimization becomes much easier once you know what needs to become faster.

How to Avoid Processing the Same Message Twice

Using a message broker?

Assume a message might arrive more than once.

One common approach is to make the consumer idempotent.

For example:

if (await processedMessages.ExistsAsync(message.Id))
{
    return;
}

await ProcessMessageAsync(message);

await processedMessages.AddAsync(message.Id);
How to Avoid Processing the Same Message Twice

An idempotent handler produces the same result even when the same message is delivered repeatedly.

Common techniques include:

  • Store processed message IDs
  • Use unique database constraints
  • Use idempotency keys
  • Make updates naturally idempotent
  • Execute state changes and deduplication atomically where needed

At-least-once delivery means duplicates are part of the design problem.

If the same message arrives twice, processing it twice should not create two different business outcomes.

How to Avoid Hardcoding Application Settings

Have values that change between environments?

Put them in the configuration.

Instead of:

var apiUrl = "https://prod-api.example.com";
var timeout = 30;

Read them from configuration:

{
  "ExternalApi": {
    "Url": "https://api.example.com",
    "TimeoutSeconds": 30
  }
}

Then bind them to options:

builder.Services.Configure<ExternalApiOptions>(
    builder.Configuration.GetSection("ExternalApi"));
How to Avoid Hardcoding Application Settings

Configuration works well for:

  • URLs
  • Timeouts
  • Feature settings
  • Connection strings
  • Environment-specific values

Secrets should usually live in a dedicated secret store rather than source code.

If a value changes without requiring a code change, configuration is usually a better home for it.

How to Update Rows Without Loading Them in EF Core

Need to update many rows in EF Core?

You don't always need to load them first.

Instead of:

var users = await context.Users
    .Where(x => !x.IsActive)
    .ToListAsync();

foreach (var user in users)
{
    user.Status = UserStatus.Disabled;
}

await context.SaveChangesAsync();

You can update matching rows directly:

await context.Users
    .Where(x => !x.IsActive)
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(x => x.Status, UserStatus.Disabled));
How to Update Rows Without Loading Them in EF Core

ExecuteUpdateAsync() is useful when:

  • You need to update many rows
  • You don't need the entities in memory
  • You don't need change tracking
  • The update can be expressed as a database operation
  • You want to avoid unnecessary round trips and allocations

The update runs directly in the database.

If you don't need the entities, loading them before updating them is optional.

How to Avoid Loading an Entire Table into Memory

Need only active customers?

Filter them in the database.

Instead of:

var customers = await context.Customers.ToListAsync();

var activeCustomers = customers
    .Where(x => x.IsActive)
    .ToList();

Prefer:

var activeCustomers = await context.Customers
    .Where(x => x.IsActive)
    .ToListAsync();

The difference is where filtering happens.

How to Avoid Loading an Entire Table into Memory

The first approach:

  • Loads every row
  • Transfers every row
  • Stores every row in memory
  • Filters afterward

The second approach lets the database return the rows you requested.

If you need 100 customers from a table containing 1,000,000 rows, filtering before ToListAsync() tends to matter.

How to Stop Work When a Request Is Cancelled

Client disconnected while your API is still doing expensive work?

Pass the CancellationToken.

public async Task<IActionResult> GetOrders(
    CancellationToken cancellationToken)
{
    var orders = await context.Orders
        .ToListAsync(cancellationToken);

    return Ok(orders);
}

Propagate the token through operations that support cancellation:

  • Database queries
  • HTTP calls
  • Background operations
  • Delays
  • Long-running async workflows

Cancellation doesn't make every operation disappear immediately.

It signals downstream operations that the caller no longer needs the result.

If the request is gone, continuing work is often unnecessary.

How to Speed Up Read-Only EF Core Queries

Reading data that you don't plan to update?

Consider AsNoTracking().

var products = await context.Products
    .AsNoTracking()
    .Where(x => x.IsActive)
    .ToListAsync();

By default, EF Core tracks entities so it can detect changes and persist them later.

For read-only scenarios, that tracking work might not be needed.

How to Speed Up Read-Only EF Core Queries

AsNoTracking() helps by:

  • Avoiding change tracking
  • Reducing tracking-related memory usage
  • Making the intent of the query explicit
  • Working well for API reads and reporting
  • Keeping read-only queries focused on reading

If you don't plan to change the entity, consider asking EF Core not to track changes.

How to Fix a 404 Error

Getting a 404 Not Found response?

Check whether the requested resource exists.

Common causes include:

  • Incorrect URL
  • Wrong route
  • Missing endpoint
  • Deleted resource
  • Incorrect HTTP path
  • Deployment configuration

HTTP 404 means the server did not find the requested resource.

If you're requesting /api/users but your endpoint is /api/customers, changing the URL might help.

How to Find Code in Your Project

Need to find something in a large codebase?

Use search.

How to Find Code in Your Project

Depending on your IDE, you can search for:

  • File names
  • Classes
  • Methods
  • Symbols
  • Text
  • References

For text on the current page, Ctrl + F is often a good starting point.

For larger searches, use your IDE's project or solution search.

Searching is usually faster than opening every file manually.

How to Avoid an Infinite Loop

Does your loop never stop?

Give it a condition that eventually becomes false.

How to Avoid an Infinite Loop

Things to check:

  • Make sure the condition changes
  • Increment your counter
  • Update variables used by the condition
  • Use break when appropriate
  • Avoid accidental while (true) loops

A loop continues while its condition evaluates to true.

If the condition stays true forever, the loop also runs forever.