Skip to content

The Black Friday wall of shame: a decade of outages, and the tests that would have caught them

Best Buy, Costco, J.Crew, Macy's, Currys, Shopify, HSBC — a wall of shame of real Black Friday failures, what actually caused each one, and the MaxoPerf test YAML that finds it before your shoppers do.

Black Friday 2026 falls on Friday, 27 November. You have about twenty weeks.

That number is the whole argument for testing. Black Friday is not a surprise outage, a viral moment, or an act of God. It is the one traffic peak on the calendar whose date, approximate magnitude, and traffic shape are all knowable months ahead — which makes it simultaneously the most preventable category of load failure and the most humiliating one to suffer. Every year, some of the largest retailers on earth suffer it anyway.

The reason is rarely that nobody tested. It is that the thing that broke was never the thing under test. Storefront reads scale beautifully; the login path does not. The peak was rehearsed; the ramp to the peak was not. The site held; the payment processor did not. The checkout worked; the order-confirmation write path silently corrupted gift-card balances. Load does not break systems evenly — it finds the one shared resource nobody thought to point traffic at.

So run the test six to eight weeks out, not two. A test two weeks before Black Friday tells you that you have a problem and denies you the time to fix it, re-test, and deploy safely. What follows is a wall of shame: real, dated, sourced Black Friday and Cyber Monday failures going back to 2014, what actually caused each one, and a MaxoPerf test configuration that would have surfaced it.

A note on honesty before we start. Retailers almost never publish postmortems. Across every incident below, only a handful of causes were ever confirmed by the company involved. Where a cause is known, this post says so. Where it is not, this post says the cause was never disclosed — rather than inventing a plausible-sounding database that supposedly ran out of connections. The failure pattern is instructive regardless; the fake mechanism is not.

Each example below is a Taurus YAML — MaxoPerf’s test format. Two things to know before you read them: the engine is auto-detected from your entrypoint file, and locations and failure criteria are not YAML keys. You configure those in the test’s Configuration tab (or via the Platform API), which is why you will not see a locations: or thresholds: block anywhere in this post.


1. Best Buy — 2014, and again in 2025

The pattern: you tested the peak, not the ramp.

On Friday 28 November 2014, BestBuy.com went fully offline. Best Buy deliberately pulled the site down to restore performance, and a company spokeswoman attributed it to “a concentrated spike in mobile traffic.” It crashed a second time later that same day. Cause: company-confirmed.

Eleven years later, on Friday 28 November 2025, it happened again. From roughly 9:40 a.m. ET to 11:50 a.m. ET, Best Buy’s site and app failed for a large share of shoppers; Downdetector logged over 1,800 reports at peak, around 80% of them from desktop web. This time Best Buy published no explanation at all. Cause: never disclosed — and note that search engines routinely misattribute the 2014 “mobile traffic” quote to this 2025 outage. It does not belong to it. Our full write-up of the 2025 incident goes deeper.

The generalisable lesson lives in the 2014 quote: concentrated. A system can hold 40,000 concurrent users at steady state and still fall over when 38,000 of them arrive inside ninety seconds, because deals went live at a posted hour and everyone had the page pre-loaded. Autoscaling reacts in minutes; a deal drop happens in seconds.

Test the ramp rate as a first-class property, not just the plateau. Model the deal-drop cliff by compressing the whole arrival into ramp-up, then holding:

execution:
- scenario: deal-drop
concurrency: 40000 # your Black Friday peak target
ramp-up: 90s # deals go live — this is the part that kills you
hold-for: 20m # can you *stay* up, not just arrive
steps: 10 # climb in 10 discrete jumps, so the knee is legible
scenarios:
deal-drop:
variables:
base_url: https://staging.example.com
requests:
- label: GET /
url: ${base_url}/
- label: GET /deals/black-friday
url: ${base_url}/deals/black-friday
- label: GET /search
url: ${base_url}/search?q=television
- label: POST /cart/items
url: ${base_url}/cart/items
method: POST
headers:
Content-Type: application/json
body: '{"sku": "sku-doorbuster-01", "qty": 1}'

Watch requests per second on POST /cart/items during the 90-second climb. If throughput plateaus while virtual users keep climbing, you have found your ceiling — and you found it in September. See also the spike test type.

Native Taurus expresses a ramp as ramp-up plus optional steps, not as a list of arbitrary {duration, target} stages. If you need a genuinely multi-stage profile — up, down, then up harder — use executor: k6 with a k6 script and define the stages there.


2. Shopify — Cyber Monday, 1 December 2025

The pattern: the chokepoint was authentication, not the storefront.

This is the rare entry with a real, company-confirmed technical cause. On Monday 1 December 2025 — the peak day of Shopify’s largest BFCM weekend on record — merchant admin dashboards, point-of-sale terminals, and token-based API integrations failed for roughly five to six hours. Shopify stated it had “found and fixed an issue with our login authentication flow.” Storefront checkout, by Shopify’s account, stayed up. Cause: company-confirmed (mechanism beyond “login authentication flow” was never detailed). We covered it in depth here.

Read that shape carefully, because it is the single most common way a “well-tested” site dies. Product and category pages are cacheable, idempotent, and horizontally trivial. Login is a write — it mints a session, touches a shared session store, and often calls an identity provider. It saturates first, and when it does, nothing downstream of it works no matter how healthy your CDN looks.

So test the auth path as its own concern, at its own concurrency, all the way through to a real authenticated action. Ramp until the knee appears:

execution:
- scenario: merchant-login
concurrency: 12000
ramp-up: 10m # climb steadily; the knee is what you're shopping for
hold-for: 10m
scenarios:
merchant-login:
variables:
base_url: https://staging-admin.example.com
access_token: ''
requests:
- label: POST /auth/login
url: ${base_url}/auth/login
method: POST
headers:
Content-Type: application/json
body: '{"email": "${LOGIN_EMAIL}", "password": "${LOGIN_PASSWORD}"}'
extract-jsonpath:
access_token: '$.access_token'
assert:
- subject: http-code
contains: ['200']
- label: GET /admin/dashboard
url: ${base_url}/admin/dashboard
headers:
Authorization: Bearer ${access_token}
- label: POST /pos/transactions
url: ${base_url}/pos/transactions
method: POST
headers:
Authorization: Bearer ${access_token}
Content-Type: application/json
body: '{"terminal_id": "pos-load-01", "amount_cents": 1999}'

LOGIN_EMAIL and LOGIN_PASSWORD are project secrets — use dedicated load-test credentials, never a real merchant’s. The breakpoint test type turns the knee into a number you can plan capacity against, and the auth login + token capture recipe covers the token plumbing in full.


3. Costco — Thanksgiving 2019, and Black Friday 2024

The pattern: you survived the spike and died in hour three.

On Thursday 28 November 2019, Costco’s site and app went down for more than sixteen hours, spilling into Black Friday morning. Third-party analysts (Love the Sales) estimated around 2.65 million affected customers and roughly $11 million in lost sales — an estimate, not a Costco figure. Costco posted only that it was seeing “longer than normal response times.” Five years later, on Friday 29 November 2024, Costco’s site again threw internal server errors for hours. Cause: never disclosed, in either year.

Sixteen hours is not a spike. A spike test that ramps, holds for twenty minutes and drops would have passed. What kills a system over hours is different: memory that never gets reclaimed, connection pools that leak a handle per error, log volumes that fill a disk, caches that thrash once the working set exceeds RAM, replication lag that compounds. These are soak failures, and only sustained load reveals them.

Run the peak shape for longer than the event itself:

execution:
- scenario: storefront-soak
concurrency: 8000 # ~50% of measured peak, held indefinitely
ramp-up: 10m
hold-for: 12h # longer than the outage you are trying not to have
throughput: 4000 # cap RPS so the shape stays predictable
scenarios:
storefront-soak:
variables:
base_url: https://staging.example.com
requests:
- label: GET /
url: ${base_url}/
- label: GET /warehouse/inventory
url: ${base_url}/warehouse/inventory
headers:
Authorization: Bearer ${API_TOKEN}
- label: POST /cart/items
url: ${base_url}/cart/items
method: POST
headers:
Content-Type: application/json
Authorization: Bearer ${API_TOKEN}
body: '{"sku": "sku-soak-01", "qty": 1}'

Keep soak scenarios deliberately boring — sustained pressure, not scenario coverage. Then read p95 latency as a slope, not a value: a p95 that is flat at hour one and 40% higher at hour eight is a leak, even if it never breaches your threshold. Schedule it overnight with the overnight soak recipe so nobody has to babysit it.


4. J.Crew and Lowe’s — 2018, after both had already failed in 2017

The pattern: the regression came back, because nothing was watching.

On Friday 23 November 2018, J.Crew’s site failed for four to five hours from around 10:54 a.m. EST, showing shoppers a “Hang on a Sec” screen — no sign-in, no add-to-cart, no orders. Love the Sales estimated ~323,000 affected shoppers and roughly $775,000 in lost sales (again, a third-party estimate). J.Crew said only: “Due to high demand, we’re experiencing some technical difficulties.” The same day, Lowe’s took its site offline behind a “down for maintenance” page until about 2:30 p.m. ET.

Here is the damning part. Both had done exactly this the year before. Lowe’s went down on Black Friday 2017; J.Crew’s 2017 failure came three days later, on Cyber Monday. Cause: never disclosed, either year, by either company.

A one-off outage is bad luck. Failing the same way twelve months later is a missing feedback loop. Nothing in either company’s release process was measuring peak-path performance against a known-good baseline, so whatever regressed in 2017 was free to regress again in 2018. The fix is not a bigger annual load test — it is a small one that runs on every release candidate and fails the build.

execution:
- scenario: checkout-baseline
concurrency: 200 # small, fast, runs on every merge
ramp-up: 1m
hold-for: 5m
scenarios:
checkout-baseline:
variables:
base_url: https://staging.example.com
requests:
- label: GET /product
url: ${base_url}/product/sku-001
- label: POST /cart/items
url: ${base_url}/cart/items
method: POST
headers:
Content-Type: application/json
body: '{"sku": "sku-001", "qty": 1}'
- label: POST /checkout
url: ${base_url}/checkout
method: POST
headers:
Content-Type: application/json
body: '{"cart_id": "cart-ci-01"}'

The YAML is the easy half. The half that matters is in the Configuration tab: set failure criteria per request label — say, POST /checkout p95 above 300 ms, or overall error rate above 0.5% — so the run comes back failed, not finished. Then trigger it from CI and let the run status fail the build. That is the whole CI-gated performance test pattern, and it is what turns a baseline regression test into something that actually stops a repeat.


5. Argos, Currys PC World, Tesco Direct, Net-a-Porter, GAME — UK, Black Friday week 2014

The pattern: reads were fine; the write path collapsed.

Britain’s first mass-market Black Friday was a rout. Tesco Direct and Currys PC World hit trouble in the early hours; Currys resorted to queueing shoppers rather than serving them. Argos was still failing intermittently at 18:45 — nine hours in — with shoppers reporting they could browse fine but could not get through checkout. Two days earlier, Net-a-Porter had opened its sale early and gone down within the hour, with users reporting failures specifically at the payment page. GAME reported problems too. Police were called to physical Tesco stores over crowd scuffles. Cause: reported as demand overload; no company published a technical postmortem.

Notice what every one of those reports has in common. The catalogue worked. The search worked. The thing that broke was the last mile — the stateful, non-cacheable, write-heavy tail of the journey where every request is unique and nothing can be served from an edge.

Which is exactly why a load test that hammers GET / proves nothing. You need unique data per virtual user, so no cache can rescue you and no two shoppers contend for the same cart:

execution:
- scenario: full-journey
concurrency: 6000
ramp-up: 5m
hold-for: 25m
scenarios:
full-journey:
data-sources:
- path: shoppers.csv
variable-names: email,password,sku
random-order: true
requests:
- label: GET /search
url: https://staging.example.com/search?q=${sku}
- label: GET /product
url: https://staging.example.com/product/${sku}
- label: POST /auth/login
url: https://staging.example.com/auth/login
method: POST
headers:
Content-Type: application/json
body: '{"email": "${email}", "password": "${password}"}'
- label: POST /cart/items
url: https://staging.example.com/cart/items
method: POST
headers:
Content-Type: application/json
body: '{"sku": "${sku}", "qty": 1}'
- label: POST /checkout/pay
url: https://staging.example.com/checkout/pay
method: POST
headers:
Content-Type: application/json
body: '{"payment_method": "test-card", "sku": "${sku}"}'

Upload shoppers.csv as a data entity with more rows than you have virtual users, so nothing recycles. Then set failure criteria per label. A run where GET /search sits at 40 ms and POST /checkout/pay sits at 9 seconds is a passing run by any global average — and a catastrophe on the day.


6. Currys — Black Friday, 27 November 2020

The pattern: it did not go down. It went wrong.

The most interesting failure on this wall is not an outage. On Friday 27 November 2020, Currys’ site buckled under demand, and in the process customers had gift-card balances drained — hundreds of pounds, in one reported case £450 — for orders that never completed. Some shoppers received confirmation emails for orders that did not exist. Refunds took until the following Wednesday. Currys acknowledged a demand surge plus an order-processing “technical fault.” Cause: company-acknowledged as a fault; the mechanism was never disclosed.

This is the failure mode nobody load tests for, because every dashboard stayed green. Requests returned 200. Latency was survivable. And the system was quietly taking money without creating orders — a partial write, a non-idempotent retry, a saga that debited before it committed. Under load, timeouts and retries stop being rare, and any write path that is not idempotent will eventually be executed twice or half.

So test correctness under load, not just latency under load. Give each virtual user a unique idempotency key, deliberately replay it, then read the order back and check the state you expect actually exists:

execution:
- scenario: order-integrity
concurrency: 2000
ramp-up: 3m
hold-for: 15m
scenarios:
order-integrity:
variables:
order_id: ''
data-sources:
- path: idempotency-keys.csv
variable-names: idem_key,gift_card_id
random-order: false
requests:
# 1. Place the order, capturing the id the server assigns
- label: POST /orders
url: https://staging.example.com/orders
method: POST
headers:
Content-Type: application/json
Idempotency-Key: ${idem_key}
body: '{"gift_card_id": "${gift_card_id}", "amount_cents": 45000}'
extract-jsonpath:
order_id: '$.order_id'
# 2. Replay the *same* key — a duplicate must not create a second order
# and must not debit the gift card twice
- label: POST /orders (replay)
url: https://staging.example.com/orders
method: POST
headers:
Content-Type: application/json
Idempotency-Key: ${idem_key}
body: '{"gift_card_id": "${gift_card_id}", "amount_cents": 45000}'
# 3. Read it back — if money moved, the order must exist
- label: GET /orders/{id}
url: https://staging.example.com/orders/${order_id}
assert:
- subject: http-code
contains: ['200']
# 4. And the balance must reflect exactly one debit
- label: GET /giftcards/{id}/balance
url: https://staging.example.com/giftcards/${gift_card_id}/balance

If extract-jsonpath fails to populate order_id, step 3 requests /orders/ and the error rate on that label goes to 100% — which is precisely the signal you want. Set a failure criterion on the GET /orders/{id} label’s error rate at zero. The correlation and dynamic values recipe covers extraction in detail.


7. Macy’s (2016, 2017) and HSBC UK (2023)

The pattern: the thing that failed was not your code.

Macy’s, Friday 25 November 2016: roughly five hours of a “temporary shopping jam” throttle page and a broken add-to-cart, which Macy’s confirmed as high volume slowing the site. Macy’s again, Friday 24 November 2017: for several hours, in-store credit-card and gift-card processing failed across Chicago, Washington D.C., San Diego and other markets. Shoppers photographed abandoned carts. Macy’s said its systems were “over capacity” and explicitly not breached. Cause: company-confirmed as capacity — whether the fault sat in Macy’s systems or its card processor was never detailed.

Then, Friday 24 November 2023 — the UK press called it “Blackout Friday” — HSBC UK went down from around 07:00 GMT until after 23:00. Online and mobile banking failed, and so did debit, credit and contactless card payments at the point of sale. HSBC called it “an internal system issue.” Cause: company statement only; no postmortem.

The lesson is uncomfortable: on Black Friday, a large fraction of your revenue path runs on infrastructure you neither own nor can scale. Payment authorisation, tokenisation, fraud scoring, loyalty lookups, tax calculation, address validation — every one is a synchronous third-party call sitting directly in your checkout. Your capacity is the minimum across all of them, not your own.

Test the payment leg as a rate, because that is how processors think and how they rate-limit. Use rate-controlled load — throughput sets the target requests per second, while concurrency merely caps how many virtual users may be in flight to sustain it:

execution:
- scenario: payment-authorisation
throughput: 900 # target authorisations per second at peak
concurrency: 3000 # ceiling on in-flight virtual users
ramp-up: 5m
hold-for: 30m
scenarios:
payment-authorisation:
variables:
base_url: https://staging.example.com
requests:
- label: POST /payments/authorize
url: ${base_url}/payments/authorize
method: POST
headers:
Content-Type: application/json
body: '{"card_token": "${CARD_TOKEN}", "amount_cents": 4999}'
assert:
- subject: http-code
contains: ['200']
- label: POST /giftcards/redeem
url: ${base_url}/giftcards/redeem
method: POST
headers:
Content-Type: application/json
body: '{"gift_card_id": "gc-load-01", "amount_cents": 1000}'
- label: POST /loyalty/points
url: ${base_url}/loyalty/points
method: POST
headers:
Content-Type: application/json
body: '{"member_id": "m-load-01", "order_cents": 4999}'

Point this at your provider’s sandbox, and tell them you are testing — sending 900 authorisations per second at a live processor without warning is a good way to get your merchant account suspended on the worst possible week. Read third-party and payment dependencies before you run it, and note which of those calls could be made asynchronous, cached, or degraded gracefully rather than blocking a sale.


8. Amazon Ads — Black Friday weekend, 25–27 November 2022

The pattern: the site was fine. You just couldn’t see anything.

For roughly a day and a half over Black Friday weekend 2022, Amazon’s advertising reporting dashboard displayed around half of advertisers’ actual spend and sales. Ads kept serving and money kept moving; the numbers were wrong. Brands managing eight-figure budgets on the highest-spend weekend of the year were, as one trade outlet put it, flying blind — some throttled campaigns that were in fact performing, others overspent against phantom headroom. Amazon described it as a reporting lag. Cause: never disclosed.

Nobody load tests their analytics pipeline. It is not on the revenue path, so it never makes the test plan — right up until the moment it is the only instrument you have during an incident, and it lies to you. Ingestion queues back up, aggregation windows slip, and a dashboard that is silently three hours stale looks identical to a dashboard that is fine.

Drive the write volume of a real peak, then query the read side while the writes are still landing and check the aggregate actually moves:

execution:
- scenario: reporting-under-load
concurrency: 4000
ramp-up: 5m
hold-for: 45m
scenarios:
reporting-under-load:
variables:
base_url: https://staging.example.com
requests:
# Fire the events a real peak would generate
- label: POST /events/order-placed
url: ${base_url}/events/order-placed
method: POST
headers:
Content-Type: application/json
Authorization: Bearer ${API_TOKEN}
body: '{"order_cents": 4999, "campaign_id": "cmp-load-01"}'
# Then ask the dashboard what it thinks just happened
- label: GET /reports/revenue
url: ${base_url}/reports/revenue?window=5m&campaign_id=cmp-load-01
headers:
Authorization: Bearer ${API_TOKEN}
assert:
- subject: http-code
contains: ['200']

Then do the part that catches an Amazon-Ads-shaped failure: compare the revenue your dashboard reports against the count of POST /events/order-placed requests in MaxoPerf’s own run artifacts. The load generator knows exactly how many events it sent. If your reporting says half that, you have reproduced the 2022 outage in staging, in September, for free. Set a failure criterion on GET /reports/revenue p95 as well — a reporting endpoint that degrades from 200 ms to 30 seconds under write load is useless in a war room.


9. Cloudflare — 18 November 2025 (ten days before Black Friday)

The pattern: your dependency’s dependency.

This one is not a Black Friday outage, and it is on the wall precisely because of that. On Tuesday 18 November 2025 — ten days before Black Friday — Cloudflare’s core proxy failed globally for hours, taking down a meaningful slice of the web with it. Uniquely on this list, there is a genuine postmortem: a database permissions change caused the Bot Management feature file to roughly double in size, exceeding a preallocated limit and crashing the traffic-routing software. Not an attack, not a traffic surge. A configuration change. Cause: confirmed, with a published postmortem. We analysed it here.

Ten days either way and that is a Black Friday story. And there is nothing you can load test about someone else’s config deploy. What you can test is whether you know how your system behaves when the edge is gone — whether your origin can survive a cache-miss storm, whether your fallbacks fall back, whether your status page is hosted behind the thing that just died.

Run the same requests through the edge and directly against your origin, side by side, and compare:

execution:
- scenario: edge-vs-origin
concurrency: 1000
ramp-up: 3m
hold-for: 15m
scenarios:
edge-vs-origin:
requests:
- label: GET / (via CDN edge)
url: https://staging.example.com/
- label: GET / (origin, bypassing CDN)
url: https://origin-direct.staging.example.com/
headers:
Host: staging.example.com
- label: GET /product (origin, bypassing CDN)
url: https://origin-direct.staging.example.com/product/sku-001
headers:
Host: staging.example.com

One caveat on that Host header: it overrides routing at the HTTP layer, but over HTTPS the TLS SNI still comes from the URL’s hostname, so an origin that routes or validates certificates on SNI will not see a clean CDN bypass. For those, point the URL at the real origin hostname instead.

The origin labels tell you what a total CDN failure costs. If GET / (origin, bypassing CDN) collapses at 1,000 VUs while the edge label is comfortable at 40,000, your real capacity on the day your CDN has a bad afternoon is 1,000.

Then add locations in the Configuration tab — pick two or three regions and set weights summing to 100 — so you also see per-region latency and routing anomalies. Locations are runner-level configuration in MaxoPerf, which is why nothing about them appears in the YAML above. The multi-region distributed load recipe walks through it.


The one that didn’t fail: Target, Cyber Monday 2015

On Monday 30 November 2015, Target.com showed shoppers a “please hold tight” banner and a virtual queue. Traffic was running at roughly twice the busiest day in the site’s history. Press coverage at the time filed it under “Target.com crashes.”

It didn’t. Target was metering traffic on purpose, and said so: the holding page was “a tactical way to help customers keep their place in their virtual line.” Cause: company-confirmed, and deliberate.

That is what competence looks like at the ceiling. Every system has a maximum; the only question is whether you discover it in advance and choose how to behave there, or whether you find out at 9:41 a.m. on Black Friday and let the load balancer decide. A queue that admits shoppers in order is a bad experience. A 503 that loses the cart is a lost customer, and Best Buy in 2014 chose the third option — deliberately pulling the site down to recover it.

You cannot pick that option unless you already know your number. That is the entire purpose of running the test in September.


What the wall teaches

  • Almost nobody publishes a postmortem. Of every retailer here, only Shopify named a subsystem. Do not plan around causes; plan around patterns — and be suspicious of any article that confidently explains a mechanism the company never disclosed.
  • Repeat offenders are the tell. J.Crew, Lowe’s, Macy’s, Costco and Best Buy each failed on Black Friday in two separate years. Surviving last year is not evidence about this year; the traffic grew and the code changed.
  • Reads scale, writes don’t. Login, cart, checkout, payment and order-confirmation are where every one of these failures actually lived. Test them at the same concurrency you test the homepage, or you have tested nothing.
  • The ramp is the risk, not the plateau. Deals go live at a posted minute. Model the ninety seconds, not the twenty minutes.
  • Green dashboards are not correctness. Currys returned 200s while draining gift cards. Amazon’s reporting was live and wrong. Assert on state, not just on status codes.
  • Your ceiling includes everyone else’s. Payment processors, identity providers, CDNs. Your capacity is the minimum across the chain.
  • Six to eight weeks, not two. Every fix on this list needed lead time: capacity provisioning, query optimisation, an idempotency key, a CI gate. None of them ship the week of.

Black Friday 2026 is on 27 November. Nothing about it is a surprise, which means every outage that happens on it will be a choice.

Pick a pattern from this wall that matches your architecture and run the test on MaxoPerf against your own environment. Then work the BFCM readiness checklist from T-6 weeks, and have a war room runbook ready for the day — because the goal was never a perfect system. It was knowing exactly where yours breaks, before twenty million shoppers find out for you.


Sources

Loss figures attributed to Love the Sales, Capacitas and Catchpoint are third-party estimates, not company-reported numbers. Downdetector counts are crowd-sourced report peaks, not audited metrics. Root causes are marked in the text as company-confirmed, reported, or never disclosed.

Questions this article answers

Why do you need to load test before Black Friday?

Black Friday is the one traffic peak whose date, rough magnitude, and traffic shape are all known months in advance. That makes it the most preventable category of outage — and the most embarrassing one to suffer. A load test run six to eight weeks out gives you time to find your capacity ceiling, fix the bottleneck, and re-test before shoppers find the ceiling for you.

Which big retailers have crashed on Black Friday?

Best Buy (2014 and again in 2025), Costco (Thanksgiving 2019 and Black Friday 2024), J.Crew (Cyber Monday 2017 and Black Friday 2018), Lowe's (2017 and 2018), Macy's (2016 and 2017), Walmart, Currys, Argos, Tesco Direct, Net-a-Porter, John Lewis, and Shopify on Cyber Monday 2025. HSBC UK's card payments failed on Black Friday 2023. Several of these failed two years running.

How far in advance should you run a Black Friday load test?

Six to eight weeks before the event. A test two weeks out tells you that you have a problem but leaves no time to fix it, re-test, and deploy safely. Re-run a confirmation test two weeks out once the fix has landed.

What actually causes Black Friday website crashes?

Very few retailers ever publish a technical postmortem, so the honest answer is that most causes were never disclosed. Where a cause was confirmed, it was usually a chokepoint rather than the site as a whole — Shopify confirmed a login authentication flow issue in 2025, Best Buy cited a concentrated spike in mobile traffic in 2014, and Macy's said its payment systems went over capacity in 2017.