Wicked Smart Data
LearnArticlesAbout
Sign InSign Up
LearnArticlesAboutContact
Sign InSign Up
Wicked Smart Data

The go-to platform for professionals who want to master data, automation, and AI — from Excel fundamentals to cutting-edge machine learning.

Platform

  • Learning Paths
  • Articles
  • About
  • Contact

Connect

  • Contact Us
  • RSS Feed

© 2026 Wicked Smart Data. All rights reserved.

Privacy PolicyTerms of Service
All Articles
Power Apps Performance Optimization: Delegation, Data Row Limits, and Reducing App Load Times

Power Apps Performance Optimization: Delegation, Data Row Limits, and Reducing App Load Times

Power Apps⚡ Practitioner21 min readJun 26, 2026Updated Jun 26, 2026
Table of Contents
  • Introduction
  • Prerequisites
  • Understanding Delegation: The Core Mental Model
  • Reading Delegation Warnings Like a Professional
  • What Determines Whether Something Delegates?
  • The Row Limit Settings: What They Actually Control
  • Rewriting Non-Delegable Formulas: Practical Patterns
  • Pattern 1: Search with Contains vs. StartsWith
  • Pattern 2: Filtering on Complex Conditions
  • Pattern 3: Filtering by Current User
  • Pattern 4: SortByColumns Combined with Filter
  • Reducing App Load Times: The Real Culprits

Power Apps Performance Optimization: Delegation, Data Row Limits, and Reducing App Load Times

Introduction

You've built a Power Apps canvas app that works beautifully in your development environment with 50 test records. Then you deploy it to production, connect it to a SharePoint list with 12,000 rows, and suddenly your gallery shows the wrong search results, your filter returns incomplete data, and users are complaining the app takes 8 seconds to load. Sound familiar? This is the delegation trap — and it catches almost every Power Apps developer at least once.

Performance in Power Apps isn't just about user experience. When an app processes data incorrectly because of delegation limitations, you're not just slow — you're wrong. A search that silently misses 11,500 records is worse than a search that times out, because at least a timeout tells the user something failed. The delegation problem fails silently, returning a confident-looking but deeply incomplete result set. This lesson is about understanding why this happens at a systems level, and then building the habits and formulas that prevent it.

By the end of this lesson, you'll be able to build Power Apps that handle real production data volumes correctly and efficiently. You'll know how to diagnose delegation warnings before they become production bugs, how to architect your data queries to stay within Power Apps' processing model, and how to slash app load times with concrete techniques like named formulas, explicit column selection, and strategic use of collections.

What you'll learn:

  • What delegation is, why it exists, and how to determine whether a specific formula delegates to your data source
  • How to interpret the 500/2000 row limit warnings and fix the underlying query logic
  • Techniques for rewriting non-delegable formulas using delegable alternatives
  • How to reduce initial app load time using App.StartScreen, Concurrent(), and on-demand data loading
  • How to use collections strategically to cache reference data without bloating memory

Prerequisites

You should already be comfortable building basic canvas apps — creating screens, binding galleries to data sources, writing Filter() and LookUp() formulas, and connecting to SharePoint or Dataverse. You don't need to be an expert, but if terms like "data source," "gallery," and "formula bar" are unfamiliar, start with the Canvas Apps 101 fundamentals lesson first.


Understanding Delegation: The Core Mental Model

Before you can fix delegation problems, you need to understand why they exist. Power Apps can connect to dozens of different data sources — SharePoint, Dataverse, SQL Server, Salesforce, Excel files, and more. Each of these has a completely different query engine underneath. SQL Server speaks T-SQL. SharePoint speaks CAML queries. Dataverse has its own OData endpoint.

When you write a Power Apps formula like:

Filter(EmployeeDirectory, Department = "Engineering")

Power Apps has two choices for how to execute this:

  1. Delegable execution: Translate the formula into a native query for the data source (a SharePoint CAML query, a SQL WHERE clause, etc.), send it to the server, and get back only the matching rows.
  2. Non-delegable execution: Download all the rows from the data source into the app's memory, then run the filter locally in the browser.

Option 1 is delegation. The filter work is delegated to the data source. Option 2 is local execution — and it's where the row limit problem lives.

The problem with option 2 isn't just performance. It's correctness. Power Apps will only download a maximum of 500 rows by default (configurable to 2000) before applying your local filter. If your SharePoint list has 12,000 employees in the Engineering department, you'll get a filtered result drawn from only the first 2000 records. The app will show you results — it just won't show you all the results. There's no error. There's no warning to the user. Just silent incompleteness.

This is the delegation trap: Non-delegable formulas don't fail loudly. They return confidently wrong results.

Power Apps does show you delegation warnings in the formula bar (yellow triangles with exclamation marks), but only if you know to look for them — and many developers learn to ignore them without understanding their consequences.


Reading Delegation Warnings Like a Professional

In Power Apps Studio, when you write a non-delegable formula, you'll see a yellow warning triangle on the formula. Hovering over it shows something like: "The formula might not work correctly with large datasets. This data source may not support this operation."

There's also a dedicated way to see all delegation warnings across your app: go to File > App checker, which lists every formula with a delegation issue and the screen it appears on. Make it a habit to check this before every production deployment.

What Determines Whether Something Delegates?

Delegation capability depends on both the function AND the data source. The same formula can delegate to Dataverse but not to SharePoint. Here's a practical reference for common scenarios:

SharePoint delegation support:

// DELEGABLE — these translate to server-side CAML queries
Filter(Projects, Status = "Active")
Filter(Projects, DueDate <= Today())
Filter(Projects, Title = SearchBox.Text)  // exact match
Search(Projects, SearchBox.Text, "Title")  // SharePoint supports this

// NOT DELEGABLE — processed locally, subject to 2000 row cap
Filter(Projects, StartsWith(Title, SearchBox.Text))  // StartsWith delegates for some sources, not all
Filter(Projects, Len(Title) > 20)  // Len() doesn't delegate
Filter(Projects, Category in ["IT", "Finance", "Legal"])  // 'in' operator often doesn't delegate
SortByColumns(Filter(Projects, Status = "Active"), "CreatedDate", Descending)  // complex combos

Dataverse delegation support (generally much broader):

// DELEGABLE on Dataverse — Dataverse has the richest delegation support
Filter(Accounts, StartsWith(name, SearchBox.Text))
Filter(Orders, totalamount > 50000 && statuscode = 1)
CountRows(Filter(Orders, customerid = currentCustomer.id))

// NOT DELEGABLE even on Dataverse
Filter(Accounts, Text(createdon, "yyyy") = "2023")  // Text() doesn't delegate
Filter(Orders, Mid(ordernumber, 3, 2) = "PO")  // string manipulation doesn't delegate

Pro tip: When in doubt, test with a data source that has more than 2000 rows. A formula that appears to work with test data may silently fail in production.


The Row Limit Settings: What They Actually Control

Go to Settings > General inside Power Apps Studio, and you'll find "Data row limit for non-delegable queries." The default is 500. You can increase it to a maximum of 2000.

Many developers find this setting and think: "Great, I'll just set it to 2000 and my problems go away." They don't. Here's the reality:

  • Increasing this limit means your app downloads more data before applying local filters, which is slower, not faster.
  • It still caps at 2000. If your real data set has 5,000 matching records, you're still getting an incomplete result.
  • The correct fix is to rewrite the formula so it delegates, not to increase the limit.

The 2000 row limit does have a legitimate use case: when you know your dataset will never exceed that size and you intentionally want to load it locally (think reference tables, lookup lists, configuration data). For those scenarios, explicitly loading the data into a collection at app start is actually better practice, which we'll cover in the collections section.


Rewriting Non-Delegable Formulas: Practical Patterns

Let's work through the most common delegation problems and their solutions using a realistic scenario: a project management app connected to a SharePoint list called ProjectTracker with columns: Title (text), Status (choice), Owner (person), Budget (number), DueDate (date), Category (choice), Description (multi-line text).

Pattern 1: Search with Contains vs. StartsWith

A search box that filters the project list is extremely common. Here's the problematic version:

// NON-DELEGABLE on SharePoint — triggers row limit warning
Filter(
    ProjectTracker,
    SearchBox.Text in Title || SearchBox.Text in Description
)

The in operator for text search doesn't delegate to SharePoint. The delegable fix:

// DELEGABLE on SharePoint — uses Search() function
Search(
    ProjectTracker,
    SearchBox.Text,
    "Title"
)

Search() does delegate to SharePoint, but only against text columns and only one column at a time (or a small set). If you need to search across multiple columns, chain them differently or reconsider your data architecture.

What if you need to search both Title AND a description field? Here's an honest answer: SharePoint's delegation doesn't support multi-column full-text search natively. Your options are:

  1. Create a calculated column in SharePoint that concatenates the fields you want to search, then search against that single column.
  2. Move to Dataverse, which has richer delegation.
  3. Accept the limitation and search only the most important field.

Pattern 2: Filtering on Complex Conditions

// NON-DELEGABLE — multiple conditions with a lookup
Filter(
    ProjectTracker,
    Status.Value = "Active" && Year(DueDate) = 2024
)

The Year() function doesn't delegate. Rewrite it using date comparison:

// DELEGABLE — uses date comparison instead of Year()
Filter(
    ProjectTracker,
    Status.Value = "Active" &&
    DueDate >= Date(2024, 1, 1) &&
    DueDate < Date(2025, 1, 1)
)

This is the general pattern: replace function calls on columns with direct comparisons. Instead of Len(Title) > 20, filter on a column that already contains a pre-computed flag. Instead of Left(ProjectCode, 2) = "IT", add a Department column to SharePoint and filter on that.

Pattern 3: Filtering by Current User

// NON-DELEGABLE — User() properties don't delegate cleanly on SharePoint
Filter(ProjectTracker, Owner.Email = User().Email)

This one is tricky. On SharePoint, filtering by the current user works for person columns using a special approach:

// DELEGABLE on SharePoint — filtering by current user
Filter(ProjectTracker, Owner.Email = User().Email)

Actually, whether this delegates depends on the SharePoint connector version and list configuration. The most reliable approach is:

// Store current user email once, use it in the filter
// In App.OnStart or App.Formulas:
Set(gblCurrentUserEmail, User().Email);

// Then in your gallery:
Filter(ProjectTracker, Owner.Email = gblCurrentUserEmail)

This doesn't necessarily change delegation, but it's a best practice — calling User() inside a deeply nested formula can cause unexpected behavior, and isolating it to a variable makes it debuggable.

Pattern 4: SortByColumns Combined with Filter

// This combination often causes delegation issues on SharePoint
SortByColumns(
    Filter(ProjectTracker, Status.Value = "Active"),
    "DueDate",
    Ascending
)

On SharePoint, sorting a filtered result can break delegation. The solution: apply SortByColumns on a column that SharePoint can index, and ensure the Filter is delegable on its own first. SharePoint lists can have up to 20 indexed columns — make sure DueDate and Status are indexed in the SharePoint list settings for best results.


Reducing App Load Times: The Real Culprits

Delegation is about correctness. Load time is about perceived performance. Both matter. A correct app that takes 12 seconds to load will still get abandoned.

The App.OnStart Problem

Most Power Apps tutorials teach you to load all your data in App.OnStart:

// SLOW — everything blocks in sequence
App.OnStart:
    Set(gblCurrentUser, LookUp(UserProfiles, Email = User().Email));
    ClearCollect(colProjects, ProjectTracker);
    ClearCollect(colClients, ClientList);
    ClearCollect(colStatusOptions, Choices(ProjectTracker.Status));
    Navigate(HomeScreen)

The problem: every line in App.OnStart runs sequentially. The app sits on the loading screen until every single line completes. If ProjectTracker has 800 rows and ClientList has 300, you're fetching 1100 rows before the user sees anything.

Fix 1: Use Concurrent() for Parallel Data Loading

Concurrent() lets you run multiple operations simultaneously rather than sequentially:

// FASTER — data sources load in parallel
App.OnStart:
    Concurrent(
        Set(gblCurrentUser, LookUp(UserProfiles, Email = User().Email)),
        ClearCollect(colClients, ClientList),
        ClearCollect(colStatusOptions, Choices(ProjectTracker.Status))
    );
    Navigate(HomeScreen)

Notice that colProjects is no longer loaded in App.OnStart at all. We'll lazy-load that on the screen that needs it. The three remaining operations now run in parallel, and Navigate happens only after they complete.

Warning: Don't put operations inside Concurrent() that depend on each other. If you need gblCurrentUser to load before filtering projects, don't put both in the same Concurrent() block.

Fix 2: App.Formulas — Named Formulas Instead of Variables

Power Apps introduced App.Formulas (named formulas) as a more powerful alternative to setting global variables in App.OnStart. Named formulas are evaluated lazily — they only compute when the value is actually needed, and they automatically recalculate when their dependencies change.

// In App.Formulas (not App.OnStart)
CurrentUser = LookUp(UserProfiles, Email = User().Email);
ActiveProjects = Filter(ProjectTracker, Status.Value = "Active" && Owner.Email = User().Email);
StatusChoices = Choices([@ProjectTracker].Status);

With named formulas, ActiveProjects is only fetched when a screen actually references it. If a user navigates directly to the Settings screen without ever visiting the Projects screen, that data never loads. This is lazy evaluation, and it's a major win for apps with multiple screens where users only visit a subset.

Compatibility note: Named formulas (App.Formulas) require the app to have the "Improved performance for hidden controls" experimental feature enabled in some older environments, but are now generally available. Check your environment's Power Platform admin center for the current status.

Fix 3: Load Only What You Need with Explicit Column Selection

By default, ClearCollect(colProjects, ProjectTracker) loads every column in your SharePoint list — including columns you never display in the app. SharePoint lists often have 30, 40, even 60 columns. You're fetching all of them.

Use ShowColumns() to explicitly limit what gets loaded:

// SLOW — loads all 45 columns
ClearCollect(colProjects, ProjectTracker)

// FAST — loads only the 6 columns the app actually uses
ClearCollect(
    colProjects,
    ShowColumns(
        ProjectTracker,
        "Title",
        "Status",
        "Owner",
        "DueDate",
        "Budget",
        "ID"
    )
)

This can reduce payload size dramatically — sometimes by 70-80% — which directly translates to faster load times and less memory usage.

Important: Always include the ID column, even if you don't display it. You'll need it for updates, deletes, and lookups.

Fix 4: Defer Heavy Data to the Screen That Needs It

Instead of loading everything in App.OnStart, load data in the OnVisible property of the screen that needs it:

// ProjectsScreen.OnVisible
If(
    IsEmpty(colProjects),
    ClearCollect(
        colProjects,
        ShowColumns(
            Filter(ProjectTracker, Status.Value <> "Archived"),
            "Title", "Status", "Owner", "DueDate", "Budget", "ID"
        )
    )
)

The IsEmpty(colProjects) check means the data only loads once — on the first visit to the screen. Subsequent visits use the cached collection. This pattern gives you lazy loading with smart caching.

Fix 5: Use App.StartScreen Instead of Navigate in OnStart

// Old pattern — navigate manually after OnStart completes
App.OnStart: [your logic]; Navigate(HomeScreen)

// Better — set the start screen directly
App.StartScreen = HomeScreen

App.StartScreen tells Power Apps which screen to show first without requiring Navigate(). This eliminates one round-trip and means the app can start rendering the first screen while other background operations are still completing.


Collections as a Performance Tool: Strategic Caching

Collections are in-memory tables that live inside the app. They're not connected to a data source — they're a local copy of data. Used strategically, they're a major performance tool. Used carelessly, they bloat memory and cause unexpected behavior.

When to Use a Collection

  • Reference/lookup data that changes infrequently (status codes, department names, cost centers)
  • Data you'll filter or search locally after a one-time server fetch
  • Data the user will edit before committing changes back to the source

When NOT to Use a Collection

  • Large transactional datasets (orders, events, tickets) that change frequently
  • Data that needs to be real-time accurate (live inventory, live pricing)
  • Data over ~5,000 rows (memory constraints become an issue)

A Realistic Collection Strategy

Here's a complete data loading strategy for a project management app:

// App.Formulas — lazy-loaded, auto-refreshing
CurrentUser = LookUp(UserProfiles, Email = User().Email);

// App.OnStart — load small reference data that every screen needs
Concurrent(
    // Status choices — tiny, fast, needed everywhere
    ClearCollect(colStatusChoices, Choices([@ProjectTracker].Status)),
    
    // Department list — small reference table
    ClearCollect(colDepartments, ShowColumns(DepartmentList, "Title", "ID", "HeadCount")),
    
    // Current user's direct reports — small, personalized
    ClearCollect(
        colMyTeam,
        ShowColumns(
            Filter(UserProfiles, ManagerEmail = User().Email),
            "DisplayName", "Email", "Department", "ID"
        )
    )
);

// ProjectsScreen.OnVisible — load only when needed
If(
    IsEmpty(colProjects),
    ClearCollect(
        colProjects,
        ShowColumns(
            Filter(
                ProjectTracker,
                Status.Value <> "Archived" &&
                DueDate >= DateAdd(Today(), -90, Days)
            ),
            "Title", "Status", "Owner", "DueDate", "Budget", "Category", "ID"
        )
    )
)

Working with Collections for Local Search

Once data is in a collection, every Power Apps function works on it without delegation concerns — because it's local data, not a server query:

// This gallery formula uses colProjects (a collection)
// ALL of these work correctly regardless of row count in the collection
Filter(
    colProjects,
    (IsBlank(SearchBox.Text) || SearchBox.Text in Title) &&
    (StatusDropdown.Selected.Value = "All" || Status.Value = StatusDropdown.Selected.Value) &&
    (IsBlank(DatePicker.SelectedDate) || DueDate <= DatePicker.SelectedDate)
)

The trade-off: this works perfectly for the data that's in the collection, but the collection itself was filtered on load to a reasonable subset. You're trading completeness for performance. For most business applications — where "active projects from the last 90 days" is a reasonable working set — this is exactly the right trade-off.


Diagnosing Performance Problems: The Right Tools

Using Monitor (Power Apps Monitor)

Power Apps Monitor is your most powerful diagnostic tool. Access it from Advanced Tools > Monitor in the left navigation of Power Apps Studio, then play the app. Monitor shows you:

  • Every data source call made by the app
  • The duration of each call
  • The size of the response (in bytes)
  • Whether the call succeeded or failed

Look for:

  • Slow calls: Any data source call over 2-3 seconds is worth investigating
  • Large responses: Calls returning megabytes of data are candidates for ShowColumns() optimization
  • Repeated calls: If you see the same data source called 5 times on a single screen, you have a formula that's re-evaluating on every control change

Using the Performance Profiler

In newer versions of Power Apps Studio, go to Settings > Upcoming features > Experimental and enable Power Apps Ideas or look for App Diagnostics. The profiler shows a flamegraph-style breakdown of where time is spent during app startup.

Checking Formula Evaluation Count

If a gallery with 50 items is making your formula run 50 times when the filter changes, that's a symptom of putting expensive operations inside gallery item formulas. The rule: never call a data source directly from inside a gallery item formula. Always filter a collection or use a pre-fetched variable.

// BAD — this LookUp runs once for EVERY row in the gallery
Gallery1.Items = ProjectTracker
// And in the gallery template label:
Label.Text = LookUp(ClientList, ID = ThisItem.ClientID, CompanyName)

// GOOD — join the data before the gallery sees it
// In Screen.OnVisible:
ClearCollect(
    colProjectsWithClients,
    AddColumns(
        Filter(ProjectTracker, Status.Value = "Active"),
        "ClientName",
        LookUp(colClients, ID = ThisItem.ClientID, CompanyName)
    )
)
// Gallery uses the pre-joined collection:
Gallery1.Items = colProjectsWithClients

The first pattern makes one LookUp call per gallery row — 50 rows means 50 network calls. The second pattern makes one AddColumns pass at load time.


Hands-On Exercise

You're building a Project Dashboard app connected to a SharePoint list called ProjectTracker (assume it has 5,000 rows) and a small ClientDirectory list (200 rows). The dashboard has three screens: Home, Projects, and Reports.

Your task: Apply all the optimization patterns from this lesson. Here's the starting (unoptimized) app configuration to fix:

Current App.OnStart (broken — fix this):

ClearCollect(colProjects, ProjectTracker);
ClearCollect(colClients, ClientDirectory);
Set(gblUser, LookUp(UserProfiles, Email = User().Email));
Navigate(HomeScreen)

Current Projects Gallery formula (broken — fix this):

Filter(
    ProjectTracker,
    SearchBox.Text in Title,
    Status.Value = StatusFilter.Selected.Value
)

Step 1: Rewrite App.OnStart to use Concurrent(), eliminate the full ProjectTracker load, and load only reference data.

Step 2: Add App.Formulas with a named formula for CurrentUser.

Step 3: Add ProjectsScreen.OnVisible with a properly delegable, column-limited load of projects.

Step 4: Fix the gallery formula to operate on colProjects instead of directly on ProjectTracker, and rewrite the search to be correct (not just delegable — it's now a local collection, so delegation doesn't apply, but correctness still matters).

Step 5: Add AddColumns to pre-join client names into colProjects so the gallery doesn't need to call LookUp per row.

Sample solution for App.OnStart:

Concurrent(
    ClearCollect(
        colClients,
        ShowColumns(ClientDirectory, "ID", "CompanyName", "ContactEmail", "Industry")
    ),
    ClearCollect(colStatusChoices, Choices([@ProjectTracker].Status))
);

Sample solution for App.Formulas:

CurrentUser = LookUp(UserProfiles, Email = User().Email);

Sample solution for ProjectsScreen.OnVisible:

If(
    IsEmpty(colProjects),
    ClearCollect(
        colProjects,
        AddColumns(
            ShowColumns(
                Filter(
                    ProjectTracker,
                    Status.Value <> "Archived" &&
                    DueDate >= DateAdd(Today(), -180, Days)
                ),
                "ID", "Title", "Status", "DueDate", "Budget", "ClientID", "Owner"
            ),
            "ClientName",
            LookUp(colClients, ID = ThisItem.ClientID, CompanyName)
        )
    )
)

Sample solution for gallery Items formula:

Filter(
    colProjects,
    (IsBlank(SearchBox.Text) ||
     SearchBox.Text in Title ||
     SearchBox.Text in ClientName) &&
    (StatusFilter.Selected.Value = "All" ||
     Status.Value = StatusFilter.Selected.Value)
)

Notice how the gallery formula now uses || with IsBlank() to handle the empty search state, searches across both Title and ClientName (possible because we're filtering a local collection, not a server data source), and filters by status with an "All" escape valve.


Common Mistakes & Troubleshooting

"My delegation warning went away, but results are still wrong"

Removing the yellow triangle doesn't guarantee correctness — it guarantees that the operation will be sent to the server. If the SharePoint column you're filtering on isn't indexed, large lists will still return incomplete results or time out. Always ensure that columns used in Filter conditions are indexed in SharePoint list settings.

"I used Concurrent() but the app is still slow"

Concurrent() speeds up parallel operations, but if any single operation inside it is slow, the whole block waits for it. Use Monitor to identify which specific call is slow. Often it's a ClearCollect loading too many columns — apply ShowColumns() to the slow call.

"My collection is empty when I reference it on another screen"

Global variables and collections set in App.OnStart are available on all screens — but only after App.OnStart completes. If you're using App.StartScreen and App.Formulas, named formulas are evaluated lazily when first referenced, which is fine. But if you set a collection in ScreenA.OnVisible and try to use it on ScreenB, it won't exist unless the user visited ScreenA first. Use App.OnStart for data needed everywhere, Screen.OnVisible for screen-specific data.

"AddColumns with LookUp is still slow"

AddColumns + LookUp against a remote data source runs one query per row. Make sure your LookUp target is a local collection, not the raw SharePoint list. Load your lookup table into a collection first (like colClients in our example), then AddColumns referencing that collection. Collection-to-collection lookups are fast; collection-to-SharePoint lookups are slow.

"Users complain the data is stale"

If you cache data in Screen.OnVisible with an IsEmpty() guard, data never refreshes unless the collection is cleared. Add a refresh button that calls Clear(colProjects) and then triggers the OnVisible logic again. Or add a timestamp check: cache data for a maximum of 10 minutes, then refresh automatically.

// In Screen.OnVisible
If(
    IsEmpty(colProjects) ||
    DateDiff(gblProjectsLoadTime, Now(), TimeUnit.Minutes) > 10,
    ClearCollect(colProjects, /* your filter query */);
    Set(gblProjectsLoadTime, Now())
)

"Delegation works in my dev environment but not in production"

SharePoint lists behave differently based on the number of items. SharePoint enforces a List View Threshold of 5,000 items — queries that work on lists under 5,000 items may fail or timeout on larger lists if the filtered column isn't indexed. The fix: ensure all columns used in Filter and Search operations are indexed in SharePoint.


Summary & Next Steps

The core insight of this lesson is that performance in Power Apps is really two separate problems wearing the same face. Delegation is a correctness problem: your formulas are being executed in the wrong place, against an incomplete slice of your data. Load time is an experience problem: you're doing too much work before the user sees anything useful. Both problems have the same root cause — treating Power Apps like a traditional local application instead of a distributed system where data lives on servers.

The practical habits to carry forward:

  • Check the App Checker for delegation warnings before every release. A yellow triangle is a bug, not a suggestion.
  • Replace function calls on columns with direct comparisons. Year(DueDate) = 2024 becomes DueDate >= Date(2024,1,1) && DueDate < Date(2025,1,1).
  • Use App.Formulas for lazy-loaded named values instead of loading everything in App.OnStart.
  • Apply ShowColumns() to every ClearCollect call to limit payload size.
  • Pre-join data with AddColumns at collection load time, not inside gallery item formulas.
  • Use Monitor to find slow calls by duration and response size, not by guessing.

Next Steps in This Learning Path

  • Power Apps with Dataverse: Dataverse offers dramatically richer delegation than SharePoint. If your app is hitting SharePoint delegation walls repeatedly, this lesson will show you when and how to migrate.
  • Power Apps Component Framework (PCF): When the built-in controls and performance optimizations aren't enough, PCF lets you build custom TypeScript components with full control over rendering.
  • Power Apps + Azure API Management: For enterprise-scale data scenarios, learn to proxy your data calls through APIM for caching, throttling, and access control.
  • ALM for Power Apps: Performance optimization at development time is only half the story. Learn how to use Pipelines and solution management to maintain your optimizations across environments.

Learning Path: Canvas Apps 101

Previous

Debugging Canvas Apps: Using the Power Apps Monitor Tool and Formula Errors to Fix Issues Fast

Related Articles

Power Apps🌱 Foundation

Debugging Canvas Apps: Using the Power Apps Monitor Tool and Formula Errors to Fix Issues Fast

15 min
Power Apps🔥 Expert

Integrating Power Apps Canvas Apps with Azure API Management: Custom Connectors, Authentication, and Throttling Strategies

31 min
Power Apps⚡ Practitioner

Model-Driven Apps vs Canvas Apps: When to Use Which Platform

15 min

On this page

  • Introduction
  • Prerequisites
  • Understanding Delegation: The Core Mental Model
  • Reading Delegation Warnings Like a Professional
  • What Determines Whether Something Delegates?
  • The Row Limit Settings: What They Actually Control
  • Rewriting Non-Delegable Formulas: Practical Patterns
  • Pattern 1: Search with Contains vs. StartsWith
  • Pattern 2: Filtering on Complex Conditions
  • Pattern 3: Filtering by Current User
  • The App.OnStart Problem
  • Fix 1: Use Concurrent() for Parallel Data Loading
  • Fix 2: App.Formulas — Named Formulas Instead of Variables
  • Fix 3: Load Only What You Need with Explicit Column Selection
  • Fix 4: Defer Heavy Data to the Screen That Needs It
  • Fix 5: Use App.StartScreen Instead of Navigate in OnStart
  • Collections as a Performance Tool: Strategic Caching
  • When to Use a Collection
  • When NOT to Use a Collection
  • A Realistic Collection Strategy
  • Working with Collections for Local Search
  • Diagnosing Performance Problems: The Right Tools
  • Using Monitor (Power Apps Monitor)
  • Using the Performance Profiler
  • Checking Formula Evaluation Count
  • Hands-On Exercise
  • Common Mistakes & Troubleshooting
  • "My delegation warning went away, but results are still wrong"
  • "I used Concurrent() but the app is still slow"
  • "My collection is empty when I reference it on another screen"
  • "AddColumns with LookUp is still slow"
  • "Users complain the data is stale"
  • "Delegation works in my dev environment but not in production"
  • Summary & Next Steps
  • Next Steps in This Learning Path
  • Pattern 4: SortByColumns Combined with Filter
  • Reducing App Load Times: The Real Culprits
  • The App.OnStart Problem
  • Fix 1: Use Concurrent() for Parallel Data Loading
  • Fix 2: App.Formulas — Named Formulas Instead of Variables
  • Fix 3: Load Only What You Need with Explicit Column Selection
  • Fix 4: Defer Heavy Data to the Screen That Needs It
  • Fix 5: Use App.StartScreen Instead of Navigate in OnStart
  • Collections as a Performance Tool: Strategic Caching
  • When to Use a Collection
  • When NOT to Use a Collection
  • A Realistic Collection Strategy
  • Working with Collections for Local Search
  • Diagnosing Performance Problems: The Right Tools
  • Using Monitor (Power Apps Monitor)
  • Using the Performance Profiler
  • Checking Formula Evaluation Count
  • Hands-On Exercise
  • Common Mistakes & Troubleshooting
  • "My delegation warning went away, but results are still wrong"
  • "I used Concurrent() but the app is still slow"
  • "My collection is empty when I reference it on another screen"
  • "AddColumns with LookUp is still slow"
  • "Users complain the data is stale"
  • "Delegation works in my dev environment but not in production"
  • Summary & Next Steps
  • Next Steps in This Learning Path