Most SharePoint solutions don't fail at launch — they degrade over time as lists grow, jobs multiply, and "just one more flow" piles on. The platform has well-defined limits and protective throttling, and the difference between a solution that scales and one that grinds to a halt is whether you designed for those limits from day one. This is the consultant's field guide to keeping large SharePoint deployments fast and governable.
The 5,000-item list view threshold
The single most misunderstood limit in SharePoint. A list can hold millions of items — up to 30 million — but a single query/view cannot return more than 5,000 items in one request. Exceed it and the operation is throttled with the dreaded "this view cannot be displayed."
The threshold is about the query, not the list size. The fixes:
- Index the columns you filter and sort on. An indexed column lets SharePoint resolve a filtered query that returns fewer than 5,000 rows even when the list holds far more.
- Filter your views so each returns under 5,000 rows — by date range, status, or a calculated "owner" column.
- Page your code in batches well under the limit (e.g. 2,000) using
RowLimit/paging tokens.
# Add an index to a column with PnP PowerShell
Set-PnPField -List "Contracts" -Identity "ContractStatus" `
-Values @{ Indexed = $true }
# Page a large list safely in code (CAML + RowLimit)
<View>
<Query><Where><Eq>
<FieldRef Name='ContractStatus' /><Value Type='Text'>Approved</Value>
</Eq></Where></Query>
<RowLimit>2000</RowLimit>
</View>
Throttling: the 429 you must respect
SharePoint Online protects shared capacity by throttling apps that send too many requests too fast. You'll get HTTP 429 (Too Many Requests) or sometimes 503, with a Retry-After header telling you how many seconds to wait. The only correct response is to back off:
HttpResponseMessage resp = await httpClient.SendAsync(request);
if ((int)resp.StatusCode == 429 || (int)resp.StatusCode == 503)
{
var retry = resp.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(10);
await Task.Delay(retry);
// retry the request (with a capped exponential back-off)
}
Retry-After exactly, add exponential back-off with jitter, and reduce concurrency. Decorate requests so Microsoft can identify well-behaved traffic (below).
Decorate your traffic with a User-Agent
Microsoft asks apps to identify themselves with a structured User-Agent string (ISV|CompanyName|AppName/Version or NONISV|...). Decorated traffic is throttled more leniently and is far easier to diagnose. PnP libraries set this for you; for raw HTTP, set it yourself:
request.Headers.UserAgent.ParseAdd("NONISV|Contoso|ContractSync/1.4");
Other limits worth designing around
- Lookup column limit per view — too many lookup/person columns in one view (the limit is small, around a dozen) triggers the threshold even under 5,000 rows. Keep views lean.
- File size & upload — large uploads need chunked/resumable sessions, not a single PUT.
- Batch your API calls — use
$batch(REST) or PnPjs batching to collapse many operations into one request and stay well clear of throttling. - Avoid chatty page loads — an SPFx web part firing dozens of calls on render will be throttled and will feel slow; fetch once, cache, and reuse.
Governance: keeping it healthy over time
Performance is half the story; governance keeps the environment from sprawling into chaos. A practical checklist:
- Site provisioning — standardise new sites with site designs + the PnP provisioning engine so structure, indexes, and policies are baked in, not bolted on.
- Lifecycle — set expiration/inactivity policies so abandoned sites are reviewed and archived; orphaned sites are a security and cost liability.
- Permissions — prefer group-based access over per-item breaks; broken inheritance at scale is both a performance and a manageability problem.
- External sharing — define and enforce a tenant policy; review sharing reports.
- Capacity — monitor storage and large-list growth before they hit limits.
- App governance — track which SPFx solutions and API permissions are deployed (those grants are tenant-wide and shared).
A simple maturity test
Ask three questions of any SharePoint solution heading to production:
- Will every list view still return under 5,000 rows when this list is ten times bigger — and are the filter/sort columns indexed?
- Does every piece of code that calls SharePoint honour
Retry-After, decorate itsUser-Agent, and batch where possible? - Is the site provisioned from a standard template, with lifecycle, permission, and sharing policies defined?
If the answer to all three is yes, the solution will scale and stay governable. If not, you've just found your backlog. Designing for limits and governance isn't glamorous, but it's exactly what separates a SharePoint deployment that thrives for years from one that quietly becomes everyone's least favourite system.