skip to content

In Eloquent, how do firstOrNew(), firstOrCreate() and updateOrCreate() differ, and which of them write to the database?

level: middleimportance: must knowfreq 65%

answer

  1. search array plus values array
  2. firstOrNew returns an unsaved model
  3. firstOrCreate falls back to createOrFirst
  4. updateOrCreate saves only changed columns
  5. wasRecentlyCreated shows which branch ran

basics

~10 s

firstOrNew() returns the found row or a new unsaved model; firstOrCreate() inserts when nothing matches; updateOrCreate() inserts when missing or fills and saves the found row. Only the last two write.

solid answer

~40 s

All three search with the first array and use the second for extra columns. `firstOrNew()` returns the matching model or a **new, unsaved** instance built from both arrays, so it never writes. `firstOrCreate()` inserts when nothing matches; in Laravel 13 it does that through `createOrFirst()`, which catches a unique-constraint violation and re-selects the row, so a race between two workers is safe **if** the column has a unique index. `updateOrCreate()` calls `firstOrCreate()` and, when the row already existed, fills the second array and calls `save()`, which writes only changed columns. `wasRecentlyCreated` tells you which branch ran, and all three go through `fill()`, so the columns must be mass assignable.

go deeper

for a junior

Recall that firstOrNew does not save, firstOrCreate saves a new row, and updateOrCreate also updates an existing one.

for a middle

Explain the two arrays, the select-then-write sequence, the createOrFirst fallback and why updateOrCreate may issue no UPDATE.

for a senior

Show that race safety comes from a unique index, and switch to batched upserts when per-row round trips dominate an import.

for a principal

Decide where idempotency for imports lives, database constraints, application helpers or both, and what the team relies on under concurrency.

## Three helpers for "find it or make it" Imports and sync jobs constantly ask: does this row exist yet? Eloquent has three helpers that combine the lookup with the fallback. Each takes two arrays: **`$attributes`**, the columns used to search, and **`$values`**, extra columns used only when a new model is built (or, for `updateOrCreate`, applied either way). | Method | Existing row | No row | Database writes | |---|---|---|---| | `firstOrNew($attributes, $values)` | returns it | returns a **new, unsaved** model | none | | `firstOrCreate($attributes, $values)` | returns it | **inserts** and returns it | one insert when missing | | `updateOrCreate($attributes, $values)` | fills `$values` and **saves** | inserts and returns it | insert, or an update of changed columns | All three build models through `fill()`, so the columns involved must be mass assignable on the model. ## Tracing each one in Laravel 13 1. `firstOrNew()` runs `where($attributes)->first()`. If nothing comes back it returns `new Product(array_merge($attributes, $values))`; you decide whether to `save()` it. 2. `firstOrCreate()` runs the same select. If nothing comes back it calls `createOrFirst()`, which tries the insert first and, if the database raises a unique-constraint violation, re-selects the row on the write connection and returns that instead. Inside an open transaction the insert runs in a savepoint so the failure does not abort the outer transaction. 3. `updateOrCreate()` calls `firstOrCreate()`. When the model was found rather than created (`wasRecentlyCreated` is false), it runs `fill($values)->save()`. `save()` only writes columns that actually changed, so an unchanged row costs no UPDATE at all. `$values` may also be a closure. For `firstOrNew()` and `firstOrCreate()` it is evaluated only when a new model is needed, which avoids computing an expensive default for rows that already exist. ## The nightly feed case An electronics store imports a supplier feed every night, keyed by `sku`: ```php foreach ($feed as $row) { $product = Product::updateOrCreate( ['sku' => $row['sku']], ['name' => $row['name'], 'price_cents' => $row['price_cents'], 'stock' => $row['stock']], ); } ``` - A new SKU is inserted with all four columns. - A known SKU gets only its changed columns written, with `updated_at` bumped; if nothing changed, no UPDATE runs. - `$product->wasRecentlyCreated` tells the loop which branch ran, which is handy for counting new versus updated items in the import report. ## Races and the unique index Two workers importing overlapping feeds can both miss the same SKU in their select. What happens next depends on the schema: - **With a unique index on `sku`**, one insert wins, the other hits the constraint, and `createOrFirst()` turns that into a re-select. No duplicate, no crash. - **Without one**, both inserts succeed and the catalogue now has two rows for the same SKU. So the helper's safety comes from the database constraint, not from the method name. `createOrFirst()` can also be called directly when inserts are the common case: it saves the initial select entirely. ## Cost at scale Each call is one select plus, when needed, an insert or an update: per-row round trips. For a few hundred rows that is fine. For a feed of hundreds of thousands, a batched `upsert()` is the usual answer, at the price of skipping model events and casts. ## Details that trip people up - **Mass assignment still applies.** The helpers build models through `fill()`, so a column that is not fillable is dropped from the new row, or throws `MassAssignmentException` on a model with no fillable settings at all. - **The search array is merged into the new row.** `firstOrCreate(['sku' => 'TV-1'], ['name' => 'OLED 55'])` inserts both `sku` and `name`; you do not repeat the search columns in the values. - **Values are not part of the search.** A row with the same SKU but a different name is still "found"; only `$attributes` go into the `where`. - **`incrementOrCreate()`** is a relative for counters: it creates the row with a default count or increments the column on the existing one. - **Model events still fire.** Because the helpers create and save real models, `creating`, `created`, `updating` and `updated` observers run on the relevant branch.

  • Is `firstOrCreate()` safe when two queue workers import the same SKU at the same time?
    Only with a unique index on the searched column. Both workers may miss the row in their select; the second insert then hits the constraint, and Laravel 13's `createOrFirst()` fallback catches the `UniqueConstraintViolationException` and re-selects the winner's row. Without the index, both inserts succeed and you get duplicate products.
  • Why might `updateOrCreate()` run no UPDATE even though the row exists?
    After filling the values it calls `save()`, and `save()` on an existing model only performs an update when `isDirty()` is true. If the feed row carries the same name, price and stock as the database, nothing is dirty, so no statement runs and `updated_at` is left alone.
  • When would you pick `firstOrNew()` over `firstOrCreate()`?
    When the new model needs more work before it may be saved, such as computing a slug, attaching a relation that needs the parent object, or skipping the save for invalid rows. `firstOrNew()` gives you the unsaved instance and leaves the `save()` decision to you.

saying these in an interview costs you the question

  • firstOrNew() inserts a row when nothing matches
  • updateOrCreate() runs one atomic database statement
  • firstOrCreate() prevents duplicates even without a unique index
  • updateOrCreate() overwrites every column on each call
  • The values array is also used to search for the row