Introduction
Your Power App works perfectly with 50 test records. Then it goes to production with 50,000 records and takes 30 seconds to load. Users complain. Managers escalate. And you spend the next two weeks learning about delegation the hard way.

Performance tuning Power Apps is part science, part pattern recognition, and part knowing where to look. This guide covers the systematic approach I use to take apps from unusable to instant — reducing load times by 80-90% using techniques that work every time.
The Performance Profiling Framework
Before optimizing, measure. Here is how to identify bottlenecks:
// Power Fx: Performance measurement instrumentation
// Add this to App.OnStart to track load performance
Set(varPerfStart, Now());
// === PHASE 1: Configuration Loading ===
Set(varPhase1Start, Now());
// Load your config here...
Set(varPhase1End, Now());
// === PHASE 2: Data Loading ===
Set(varPhase2Start, Now());
Concurrent(
ClearCollect(colData1, Filter(DataSource1, Active = true)),
ClearCollect(colData2, Filter(DataSource2, Active = true)),
ClearCollect(colLookups, Choices(DataSource1.Category))
);
Set(varPhase2End, Now());
// === PHASE 3: UI Preparation ===
Set(varPhase3Start, Now());
// Set calculated variables here...
Set(varPhase3End, Now());
Set(varPerfEnd, Now());
// Performance report
Set(varPerfReport, {
TotalMs: DateDiff(varPerfStart, varPerfEnd, TimeUnit.Milliseconds),
Phase1_Config: DateDiff(varPhase1Start, varPhase1End, TimeUnit.Milliseconds),
Phase2_Data: DateDiff(varPhase2Start, varPhase2End, TimeUnit.Milliseconds),
Phase3_UI: DateDiff(varPhase3Start, varPhase3End, TimeUnit.Milliseconds)
});
Common Performance Issues
| Issue | Symptom | Typical Impact | Difficulty to Fix |
|---|---|---|---|
| Non-delegable queries | Gallery shows max 500/2000 records | High | Medium |
| Sequential data loading | App.OnStart takes 15+ seconds | High | Easy |
| Large collections in memory | App crashes on mobile | Critical | Medium |
| Formula recalculation chains | UI feels sluggish on every tap | Medium | Hard |
| Unoptimized gallery templates | Scroll lag with complex items | Medium | Easy |
| Too many controls on screen | Screen transition takes 5+ seconds | Medium | Medium |
| N+1 query pattern | ForAll + LookUp in gallery | High | Easy |
Fix 1: Delegation — The Number One Performance Killer
// ❌ NON-DELEGABLE: This only processes first 500/2000 records
// Filter runs locally on the device
Filter(
LargeTable,
StartsWith(Title, txtSearch.Text)
&& Category in ["Active", "Pending"] // 'in' is NOT delegable!
&& Year(CreatedDate) = 2026 // Year() is NOT delegable!
);
// ✅ DELEGABLE: This runs on the server
// All operations are delegable to Dataverse
Filter(
LargeTable,
StartsWith(Title, txtSearch.Text)
&& (Category = "Active" || Category = "Pending")
&& CreatedDate >= Date(2026, 1, 1)
&& CreatedDate < Date(2027, 1, 1)
);
// Common non-delegable functions to avoid:
// ❌ CountRows(Filter(...)) - use CountIf() instead
// ❌ in operator with table - use || chain instead
// ❌ Year(), Month(), Day() - use date range comparison
// ❌ IsBlank() in some connectors - test against ""
// ❌ Trim(), Lower(), Upper() - normalize data at write time
// ❌ Len() - pre-calculate and store as column
Delegation Quick Reference
{
"delegation_guide": {
"always_delegable_to_dataverse": [
"=, <>, <, >, <=, >=",
"And, Or, Not",
"StartsWith (text columns)",
"Filter with supported predicates",
"Sort, SortByColumns",
"Search (full-text search columns)"
],
"never_delegable": [
"in operator (use || chain)",
"exactin operator",
"Year(), Month(), Day(), Hour()",
"Trim(), Lower(), Upper(), Len()",
"Left(), Mid(), Right()",
"CountRows(Filter(...))",
"LookUp inside ForAll",
"Sum(), Average(), Min(), Max() with complex filters"
],
"delegation_limit": {
"default": 500,
"maximum_configurable": 2000,
"setting_path": "App Settings > Advanced > Data row limit",
"recommendation": "Set to 2000, but design queries to not need it"
}
}
}

Fix 2: Concurrent Data Loading
// ❌ SEQUENTIAL: Each call waits for the previous one
// Total time = Sum of all individual call times
ClearCollect(colEmployees, Filter(Employees, Active = true));
ClearCollect(colDepartments, Departments);
ClearCollect(colLocations, Locations);
ClearCollect(colRoles, SecurityRoles);
// Time: ~2s + ~1s + ~0.5s + ~0.5s = ~4 seconds
// ✅ CONCURRENT: All calls run simultaneously
// Total time = Time of slowest individual call
Concurrent(
ClearCollect(colEmployees, Filter(Employees, Active = true)),
ClearCollect(colDepartments, Departments),
ClearCollect(colLocations, Locations),
ClearCollect(colRoles, SecurityRoles)
);
// Time: max(~2s, ~1s, ~0.5s, ~0.5s) = ~2 seconds (50% faster)
Fix 3: Collection Management
// ❌ MEMORY HOG: Load all records into collection
ClearCollect(colAllOrders, '[dbo].[Orders]');
// Loads 100,000+ records into device memory = crash on mobile
// ✅ SMART LOADING: Only load what's needed
// Use server-side filtering + pagination
ClearCollect(
colRecentOrders,
TopN(
SortByColumns(
Filter(
'[dbo].[Orders]',
OrderDate >= DateAdd(Today(), -30, TimeUnit.Days)
&& Region = varUserRegion
&& Status <> "Cancelled"
),
"OrderDate", SortOrder.Descending
),
100
)
);
// For galleries, use direct data source reference (delegated)
// Instead of: Gallery.Items = colAllOrders
// Use: Gallery.Items = Filter(Orders, ...)
Fix 4: Gallery Optimization
// ❌ SLOW GALLERY: Complex template with lookups per row
// Each row makes 3 separate data source calls
Gallery.Items = Filter(Orders, Active = true)
// Inside template:
// LookUp(Customers, Id = ThisItem.CustomerId, Name) // call 1
// LookUp(Products, Id = ThisItem.ProductId, Name) // call 2
// CountRows(Filter(OrderLines, OrderId = ThisItem.Id)) // call 3
// With 50 visible rows = 150 extra API calls!
// ✅ FAST GALLERY: Pre-join data or use views
// Option A: Use a SQL view that joins the data server-side
Gallery.Items = Filter('[dbo].[vw_OrdersWithDetails]', Active = true)
// Option B: Pre-load lookup data into collections, use in-memory LookUp
// (Already loaded via Concurrent on App.OnStart)
// Inside template - in-memory lookups are instant:
// LookUp(colCustomers, Id = ThisItem.CustomerId, Name)
// LookUp(colProducts, Id = ThisItem.ProductId, Name)
// CountIf(colOrderLines, OrderId = ThisItem.Id)

Fix 5: Screen Navigation Performance
// ❌ SLOW: Loading data on every screen navigation
// Screen.OnVisible loads data from Dataverse every time
ClearCollect(colScreenData, Filter(BigTable, Category = varSelected));
// ✅ FAST: Conditional loading with staleness check
If(
varLastDataLoad_Screen3 = Blank()
|| DateDiff(varLastDataLoad_Screen3, Now(), TimeUnit.Seconds) > 300
|| varForceRefresh,
// Data is stale or first visit - reload
ClearCollect(colScreenData, Filter(BigTable, Category = varSelected));
Set(varLastDataLoad_Screen3, Now());
Set(varForceRefresh, false),
// Data is fresh - use cached collection
// No action needed, colScreenData still in memory
true
);
Performance Monitoring with Monitor Tool
# PowerShell: Power Apps performance monitoring setup
Write-Host "=== PERFORMANCE MONITORING TOOLS ===" -ForegroundColor Cyan
Write-Host ""
Write-Host "[1] BUILT-IN MONITOR" -ForegroundColor Yellow
Write-Host " Access: make.powerapps.com > App > ... > Monitor"
Write-Host " Shows:"
Write-Host " - Network requests with timing"
Write-Host " - Data source call duration"
Write-Host " - Delegation warnings"
Write-Host " - Formula evaluation times"
Write-Host " - Screen render performance"
Write-Host ""
Write-Host "[2] APP INSIGHTS INTEGRATION" -ForegroundColor Yellow
Write-Host " Access: App Settings > Upcoming features > Monitoring"
Write-Host " Configure: App Insights Instrumentation Key"
Write-Host " Provides:"
Write-Host " - Production telemetry (real user monitoring)"
Write-Host " - Error tracking at scale"
Write-Host " - Performance trends over time"
Write-Host " - Custom events via Trace() function"
Write-Host ""
Write-Host "[3] CUSTOM PERFORMANCE DASHBOARD" -ForegroundColor Yellow
Write-Host " Build a Canvas App that displays:"
Write-Host " - Average load time per screen (from Trace() data)"
Write-Host " - Slowest operations per day"
Write-Host " - Error frequency by app version"
Write-Host " - User session duration trends"
Performance Optimization Checklist
| Check | Impact | Effort | Priority |
|---|---|---|---|
| Set delegation limit to 2000 | Medium | 1 min | P1 |
| Replace non-delegable functions | High | 2-4 hours | P1 |
| Add Concurrent() to App.OnStart | High | 30 min | P1 |
| Remove unused data connections | Medium | 15 min | P2 |
| Use views instead of complex gallery templates | High | 2-4 hours | P1 |
| Pre-load lookup collections | High | 1 hour | P1 |
| Reduce controls per screen to <500 | Medium | 2-4 hours | P2 |
| Implement conditional data loading | Medium | 1 hour | P2 |
| Enable delayed load for off-screen controls | Low | 30 min | P3 |
| Optimize image sizes (compress, resize) | Medium | 1 hour | P2 |
| Remove unused media from app | Low | 30 min | P3 |

Architecture Decision and Tradeoffs
When designing low-code development solutions with Power Apps, consider these key architectural trade-offs:
| Approach | Best For | Tradeoff |
|---|---|---|
| Managed / platform service | Rapid delivery, reduced ops burden | Less customisation, potential vendor lock-in |
| Custom / self-hosted | Full control, advanced tuning | Higher operational overhead and cost |
Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.
Validation and Versioning
- Last validated: April 2026
- Validate examples against your tenant, region, and SKU constraints before production rollout.
- Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.
Security and Governance Considerations
- Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
- Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
- Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.
Cost and Performance Notes
- Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
- Baseline performance with synthetic and real-user checks before and after major changes.
- Scale resources with measured thresholds and revisit sizing after usage pattern changes.
Official Microsoft References
- https://learn.microsoft.com/power-apps/
- https://learn.microsoft.com/power-platform/admin/
- https://learn.microsoft.com/power-platform/guidance/
Public Examples from Official Sources
- These examples are sourced from official public Microsoft documentation and sample repositories.
- Documentation examples: https://learn.microsoft.com/power-apps/
- Sample repositories: https://github.com/microsoft/PowerApps-Samples
- Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.
Key Takeaways
- Always profile before optimizing — use the Monitor tool and App Insights to find the actual bottleneck before guessing
- Delegation is the #1 performance issue — understand which functions are delegable and restructure queries accordingly
- Concurrent() reduces load time by running data calls in parallel — typically cuts App.OnStart time by 40-60%
- Never load entire tables into collections — use server-side filtering, TopN, and pagination
- Gallery N+1 queries murder performance — pre-load lookup data or use database views that join data server-side
- Conditional data loading prevents redundant API calls — check cache freshness before reloading
- Set the delegation limit to 2000 as a safety net, but design queries that do not depend on it
- Mobile performance requires additional attention — smaller collections, fewer controls, compressed images
