krishworkstech.com

How Does Ping-Pong Flash Prevent Data Loss?

Learn how Ping-Pong Flash Storage keeps configuration data safe during power failures using dual flash sectors, atomic updates, CRC validation, versioning, and recovery mechanisms.

A pattern every embedded engineer should have in their toolbox — and why it applies far beyond firmware.

Published: July 23, 2026

Ping Pong Hero Image

Ping-Pong Flash in 30 Seconds

Power failure during flash erase/write can corrupt stored configuration.

Keep two storage areas and never destroy the previous valid copy until the new one has been safely committed.

After any unexpected reset, firmware can recover the newest valid record.

Payload first → validation information → magic word last.

TABLE OF CONTENTS

WRITTEN BY

Subhajit Roy

Subhajit Roy

Co-Founder & Embedded Engineer

Krishworks Technology Innovations

01

The problem sounds trivial

Your device needs to remember a few things across reboots. Some configuration, a pairing key, a session counter, a device ID. A few hundred bytes at most.

You reach for the obvious answer: write it to flash.

You put together a naïve implementation. It works on the bench. You ship. And then, weeks later, a customer emails you:

“The device forgot all its settings after the power blip yesterday.”

You investigate. You reproduce it. And you realize the bug isn’t a bug in the traditional sense — it’s a fundamental property of how flash memory works, and your storage code was never actually safe.

This article is about the pattern that fixes it. It’s called ping-pong sector storage, and once you understand why it works, you’ll see the same shape of solution in filesystems, databases, distributed consensus, and blockchain commit protocols.

Let’s build it up from first principles.

ping-pong-flash
02

What flash actually gives you (and doesn't)

If you’ve never worked at the flash bit level, three properties will surprise you.

icon

Writes are one-way

Flash can change bits from 1 → 0. Returning
0 → 1 requires an erase.

icon 02

Erase is coarse

You typically erase an entire sector, not individual bytes.

icon 03

Erase is slow

A power failure during erase can leave the sector corrupted.

Now consider what your naïve implementation looked like:

save_settings():
erase_sector(settings_sector) ← 200 ms window of vulnerability
write_bytes(settings_sector, buf, len)

That first line is your enemy. Any power cut inside that 200 ms window destroys your settings entirely. And the second line has its own vulnerability window — power cuts between the erase and the write leave you with a sector full of 0xFF , which your read routine interprets as “factory fresh.”

If your device has any chance of being in the field where operators can unplug it, where batteries can droop, where brown-outs happen — this design is a time bomb.

Building an embedded device that can't afford data loss?

Krishworks helps teams design reliable firmware, hardware and IoT products for production.

03

The insight: atomic-looking commits from non-atomic hardware

We can’t make individual flash operations atomic — that’s a hardware constraint we don’t control. But we can build a scheme on top of the raw flash where every possible failure state leaves the system in a known-good position.

The trick has two parts:

01

Bigger records

When we want to save new settings, we write them to a different location. The old settings stay intact until the new ones are fully committed.

02

Wider deployment

Every saved snapshot carries a monotonically-increasing sequence number. On boot, we scan our storage area and pick the highest-versioned entry that also passes integrity checks (magic word + CRC). Corrupt / partial entries are silently skipped.

If a power cut happens between saves, the previous good copy is what gets loaded next boot. If it happens during the save of a new copy, that new copy fails its integrity check on boot and gets ignored — and again the previous copy is loaded.

The system never lands in “everything is gone.” That’s the invariant we’re defending.

04

The ping-pong design

Take two flash sectors, side by side. Call them A and B. Each holds a ring of fixed-size records — say 21 records of 192 bytes each, fitting neatly into a 4 KB sector.

Sector A (4 KB, 21 slots)                Sector B (4 KB, 21 slots)
┌────────────────────────────────┐ ┌────────────────────────────────┐ │ slot0 seq=1 (initial) │ │ slot0 <empty, 0xFF> │ │ slot1 seq=2 (config change)│ │ slot1 <empty> │ │ slot2 seq=3 (adopted pan) │ │ slot2 <empty> │ │ slot3 seq=4 (key rotation) │ │ ... │ │ ... │ │ │ │ slot20 seq=21 (last slot) │ │ │ └────────────────────────────────┘ └────────────────────────────────┘ ACTIVE IDLE

An update never touches an existing slot. It writes to the next empty slot within the active sector. That’s just a program operation — no erase, no danger window. The seq counter increments.

Boot scans both sectors, finds every slot that has a valid magic word and correct CRC over its payload, and picks the winner: the one with the highest seq. That becomes the loaded state.

05

The commit "trick" — magic word programmed last

Here’s the elegant part. Each record has a small header, and the first thing in the header is a 4-byte magic word (e.g. 0x3153564E for the ASCII “NVS1”). The rule is: write the payload first, write the magic word last.

Why? Because if power dies between writing the payload and writing the magic word, the slot on flash will have the payload bytes but the magic word will still read as 0xFFFFFFFF (the erased state). Your boot-time scanner treats “magic mismatch” as “this slot is empty, skip it.” So the partially-written slot behaves exactly like a never-touched one, and your previous good record still wins.

This is the moral equivalent of a commit point in a database transaction — the moment when a bunch of separate operations suddenly become observable as a single atomic thing. The magic word is your commit fence. Everything before it is speculative; after it, the change is real.

Consequence : every update either fully succeeds or fully appears not to have happened. There is no half-committed state that survives to be read.

06

The tricky bit: when a sector fills up

Eventually you use up all 21 slots in sector A. What now?

Your next update needs to land somewhere. And you eventually need to reclaim the space in A. The temptation is to erase A right away — but if you erase A, you lose your only good copy while writing the new one to B. Any power cut here is catastrophic.

The correct sequence:

Step 1 — before:                          Step 2 — write new record into B[0]
┌───────────────────────┐ ┌───────────────────────┐
│ A: slot0..20 (seq=21) │ ACTIVE │ B: slot0 (seq=22) NEW │ (freshly committed)
│ B: all 0xFF │ IDLE │ A: slot0..20 (seq=21) │ (still present)
└───────────────────────┘ └───────────────────────┘

Step 3 — erase A Step 4 — steady state
┌───────────────────────┐ ┌───────────────────────┐
│ A: all 0xFF (erased) │ IDLE │ B is now ACTIVE │
│ B: slot0 (seq=22) │ ACTIVE │ A is IDLE, awaits next│
└───────────────────────┘ └───────────────────────┘
rollover

Every step is crash-safe. Let’s walk through each failure point:

icon

Cut between step 1 and 2 (before B[0] is fully written)

A has the latest record. B is empty or partial. Boot picks A.

icon 02

Cut between step 2 and 3 (B[0] fully written, A not yet erased)

Both sectors have valid records now. Boot picks the higher seq which is in B. On the next update we notice A still has stale records and can proceed to erase it (or just erase it eagerly on boot).

icon 03

Cut mid-erase (step 3)

A is partially-erased garbage. Its records will fail their CRC or magic-word check. B[0] is intact. Boot picks B[0].

Now consider what your naïve implementation looked like:

At no point in the sequence is there zero valid records anywhere on the chip. That’s the invariant. The commit is the magic-word write in step 2, and everything after that is idempotent cleanup — if it gets interrupted, the next boot can safely retry the same cleanup.

07

Wear leveling — a lovely side effect

Flash cells wear out. Each sector has an endurance rating — typically 100,000 program/erase cycles for consumer QSPI flash. If you erase a sector 30 times per second, you’ll burn it out in a day. So write-heavy applications need to spread writes across many sectors to extend life.

In the ping-pong scheme, we only erase a sector when it fills up — which happens every 21 commits (in our example). Between those, all we do is program new slots, which doesn’t touch the erase counter at all. And the two sectors take turns getting erased, so wear is naturally balanced across them.

The endurance math:

21 commits/erase × 100,000 erases × 2 sectors ≈ 4,200,000 commits

Assuming a very generous 100 commits per day (which is a lot for any config workload), you’d need over a hundred years to hit the endurance limit. On any realistic device this is a non-concern.

If you’re building an embedded or IoT product and reliability matters, let’s talk about your challenge.

08

What if you need more space?

A single record in our example is 192 bytes. Some applications need more — cryptographic key material, cached tables, logs. Two options:

OPTION 01

Bigger records

Each still fits within one sector, but with fewer slots per sector. A 512-byte record leaves 8 slots per 4 KB sector. Endurance and crash-safety semantics are unchanged.

OPTION 02

Wider deployment

Use more than two sectors. Instead of A/B, use A/B/C/D. Same rotation, same semantics, more headroom.

The invariants — magic-word commit fence, monotonic seq counter, boot scanner picks the winner, at-least-one-valid-record-always — remain identical. The pattern scales.

09

When this pattern generalizes

Ping-pong storage is a specific instance of a much more general technique that shows up everywhere in systems engineering. The same underlying idea appears again and again:

SYSTEM SAME UNDERLYING IDEA
Flash storage Write new record → commit with magic
Filesystem Write new file → atomic rename
Database Write changes → commit transaction
Bootloader Write image B → switch active partition
Distributed system Prepare → commit

The invariants — magic-word commit fence, monotonic seq counter, boot scanner picks the winner, at-least-one-valid-record-always — remain identical. The pattern scales.

Same principle everywhere :

Never destroy the old valid state until the new state is safely committed.

10

When you should NOT reach for this

This pattern isn’t free — it costs disk space (two sectors instead of one), adds boot-scan latency (small, but real), and complicates your storage code. If you have any of these situations, simpler approaches may serve better:

Don’t use Ping-Pong Storage when…

But for the common Embedded case — a device with no filesystem, no RTOS storage layer, no battery backup, holding a few KB of state that MUST survive arbitrary power loss — ping-pong is the classic answer, and it’s remarkable how many people reinvent it (badly) without realizing there’s a well-understood pattern already.

5 Rules to Remember

Five principles that generalize beyond flash storage:

Ping-pong storage is a small pattern. But the discipline it embodies — being paranoid about the medium’s failure modes, deferring destructive operations, making commits atomic even when the underlying hardware isn’t — is the foundation of every reliable storage system I’ve ever seen, from a $2 microcontroller with an external flash chip to a distributed database serving billions of requests a second.

Learn the small pattern well, and you’ll recognize the shape of the bigger ones wherever you find them.

If you build embedded systems and hit similar challenges around durable state, crash safety, or unusual storage media, I’d love to hear how you solved them.

Subhajit Roy

Subhajit Roy

Co-Founder & Embedded Engineer

Krishworks Technology Innovations

SHARE AT
grok-1
chatgpt-6
gemini-icon-logo
perplexity-color-1-

Explore more Articles

Scroll to Top
  • Schematic design
  • PCB and schematic source files
  • Assembling drawing files
  • Providing prototype/sample and production PCB service
  • Testing and validation of designed hardware
  • HIPAA
  • Azure Key
  • Management
  • ES, Checksum,
  • MD5sum
  • AWS
  • Azure
  • GCP
  • DigitalOcean
  • Kotlin
  • Python
  • Tensorflow
  • Computer Vision
  • ECG
  • SPO2
  • Heart Rate
  • Glucometer
  • Blood Pressure
  • UX UI Process
  • Figma and FigJam
  • Adobe Suite
  • Selenium Java
  • Postman
  • Swagger
  • Jmeter
  • SQL
  • Java Scripter
  • Test ng
  • Extents Reports
  • Flutter
  • Java
  • Kotlin
  • Swift
  • Dart
  • React JS
  • Python
  • NodeJS
  • Django
  • HTML, CSS, JS
RDBMS
  • PostgreSQL
  • Oracle
  • MySQL
  • MariaDB
No SQL Based
  • MongoDB
  • GCP
  • FirestoreDB
  • DynamoDB
  • Azure
  • CosmosDB
  • AWS