Two red and blue document tiles travel on converging rails toward one brass pointer pivot, with separate stacked blocks behind it. Two red and blue document tiles travel on converging rails toward one brass pointer pivot, with separate stacked blocks behind it.

Concurrent Writes: A Reference Update Simulation

Two writers can store separate objects yet collide over one shared reference. Run six deterministic schedules, inspect the trace and compare unchecked with conditional updates.

A concurrent writes reference update simulation makes one coordination problem visible: two writers can store separate objects safely yet still compete over one shared name. Our tiny Python model compares an unchecked overwrite with an atomic conditional update. It runs locally, uses no Git commands and changes no repository.

We executed all six schedules that preserve each writer’s read-before-write order. Across those schedules, unchecked writes lose an accepted object from the final parent chain four times. The conditional path rejects four stale attempts and loses no accepted history in this model. These are exhaustive counts for the defined toy, not probabilities for real repositories.

Why GitHub’s new architecture makes a useful teaching hook

GitHub’s 5 November engineering report describes internal testing with 5,000 concurrent agents and 7,200 pushes per second to one repository. It separates immutable object data from coordinated reference updates. Those vendor-reported capacity figures are not developer productivity, a public service guarantee or a benchmark we reproduced.

A separate historical chart in that report gives one repository’s March and May 2026 average daily requests as 44 million and 181 million. We calculate 181 ÷ 44 ≈ 4.11 times. Requests, pushes and agent counts are different units. Keep them in separate rows rather than turning them into a single scale score.

GitHub-reported historical averages for one repository: 44 million daily requests in March 2026 and 181 million in May 2026. Author calculation 181 divided by 44 equals about 4.11 times. These are two monthly averages, not a daily time series.
Figure 1. Historical source context only. It is separate from the two-writer classroom model and the report’s internal push-capacity tests.

Define objects, references and the invariant

Our object map starts with one root object. Each proposed object records a writer label and the parent reference that writer observed. Its key is a hash of that content. Once stored, the object is not edited. Creating a different parent produces different content and therefore a different key in this teaching model.

The shared reference holds the key of the current object. Following parent links from that key gives the final accepted chain. The invariant we check is narrow: an object whose write was accepted must remain reachable from that final chain. We do not count an unaccepted proposal as accepted work merely because it exists in storage.

This definition is intentionally simpler than Git. A writer label stands for a proposed change, not a real patch. We model one parent and one reference. We do not run a distributed service, parse commits or simulate merge semantics. The aim is to inspect a correctness rule before discussing throughput.

Start with the collision both writers can see

In the schedule A-read, B-read, A-write, B-write, both writers observe root. A stores a new object whose parent is root, then updates the reference to A. B also stores an object whose parent is root. The object map can retain both objects without one replacing the other.

With an unchecked update, B then moves the reference to B. The final parent chain is B → root. A remains stored, but it is no longer reachable through that reference’s parent chain. The problem is the accepted update’s place in shared history, not deletion of the stored object.

Side-by-side timeline for A-read B-read A-write B-write. Both proposals parent root. Unsafe B overwrites A; conditional B expects root, sees A and is rejected. Both object maps still retain the root and two proposals.
Infographic. One scripted collision shows why storing a proposal and accepting a shared-reference change are separate decisions.

Add the expected old value to the update

For the conditional path, the model accepts a write only when the current reference still equals the value that writer read. The comparison and assignment are one atomic modeled operation. If A has already changed the reference, B’s expectation of root is stale and B is rejected.

An ordinary application-level “if current equals old, then assign” is not automatically atomic across real threads or machines. A production implementation needs a suitable database, lock, transaction or atomic primitive. Our sequential event simulator assumes this primitive so it can isolate the ordering problem.

The official git-update-ref reference documents a related expected-old-object check: supplying the old object verifies the current reference before the update. Read the manual before applying any repository operation. Our download invokes none of those commands and is suitable for studying the idea without modifying your branches.

Enumerate all six schedules instead of sampling a race

Each writer has two events: read, then write. There are six interleavings that keep A’s read before A’s write and B’s read before B’s write. Two are serial: one writer completes before the other reads. In those cases, the second proposal can name the first accepted object as its parent.

ScheduleUnsafe loses accepted history?Conditional stale rejections
AR AW BR BWNo0
AR BR AW BWYes1
AR BR BW AWYes1
BR AR AW BWYes1
BR AR BW AWYes1
BR BW AR AWNo0
Executed deterministic enumeration. R means read and W means write. These six schedules are the complete defined model, not six randomly observed production events.

The four overlapping schedules share the same starting expectation. Unchecked updates accept both writes and lose one accepted object from the final chain. Conditional updates accept one and reject the stale one. The two serial schedules accept both writes in either mode without losing accepted history.

Executed toy totals across six schedules: unsafe accepts 12 of 12 attempts with zero stale rejections, while conditional accepts eight and rejects four stale attempts. Unsafe loses accepted history in four schedules; conditional loses none.
Figure 2. Rejecting stale work is a correctness outcome, not a performance failure. Each mode has twelve attempted writes across six scripted schedules.

Read the output without inventing a success rate

The outcome file contains twelve runs: six schedules for each mode. The step trace contains forty-eight events. Each run stores three objects, including root. A rejected proposal remains in the model’s object map, so an object count cannot tell you how many reference changes were accepted.

Tech Consulting Tailored to Your Coding Journey

Get expert guidance in coding with a personalized consultation. Receive unique, actionable insights delivered in 1-3 business days.

Get Started

Do not label four of six schedules as a 66.7% real-world failure probability. We assigned no probability distribution to event order. Likewise, eight accepted conditional writes are not evidence of worse throughput than twelve unsafe accepts. The accepted operations satisfy different correctness requirements.

The code asserts the exact expected totals. It also checks that no accepted conditional object leaves the final parent chain. Those checks make a change to the simulator reviewable. If you extend the model, revise the invariant and expected results deliberately instead of changing the answers merely to make an assertion pass.

Handle rejection as new work, not blind resubmission

A stale rejection tells the writer that its starting assumption changed. A real workflow must read the new state, inspect the competing change and decide whether its own work can be recomputed or merged. Reusing the old expectation repeatedly cannot make the conflict disappear.

Our supplied model stops at rejection. It does not automatically rebase, retry or merge. A useful extension is to add an explicit retry event with a new parent and trace its outcome. Keep retry limits and conflict decisions visible. Do not assume an arbitrary business operation can safely be repeated.

Download and reproduce the classroom model

python3 reference_simulator.py --out results

Use Python 3.9 or later in a new working folder. The standard-library script writes three CSVs to the selected results directory. Compare them with the ZIP’s expected files. It needs no package installation, credentials, network access or real repository. Avoid pointing the output option at a folder containing files you want to preserve with the same names.

For related evidence discipline, use our Git installation inventory checklist. To compare complete review effort rather than first-render speed, see the AI browser testing benchmark. Neither supplies performance evidence for this simulator.

Method, limitations and reuse

Research checked and model executed 11 November 2026. The source-scale ledger transcribes distinct reported measures. The original simulator enumerates event order deterministically; there are no random delays or wall-clock timing claims. Its conditional operation is assumed atomic and the assertions apply only to this defined state machine.

The model omits durability, replication, consensus failures, garbage collection, multiple references, ABA-style state reuse and real Git commit semantics. It cannot reproduce GitHub’s capacity test. Suggested citation: Learn Coding USA, “Concurrent Writes: A Reference Update Simulation,” 11 November 2026. Credit the linked source for platform facts and this report for the teaching code and trace. Send corrections through our contact page.