In Eloquent, what do find(), findOrFail(), first() and firstWhere() return when no matching row exists?
answer
- null versus an exception
- find() adds where on the primary key
- ModelNotFoundException from the OrFail variants
- firstWhere = where()->first()
- array of ids returns a Collection
basics
~10 sfind(), first() and firstWhere() return null when nothing matches, while findOrFail() and firstOrFail() throw ModelNotFoundException. Choose the null-returning form when absence is normal and the OrFail form when absence is an error.
solid answer
~40 s`find($id)` looks the row up by primary key and returns the model or `null`; given an array of ids it returns a Collection that simply lacks the missing ones. `first()` returns the first row of whatever query you built, or `null`, and `firstWhere('sku', $sku)` is shorthand for `where('sku', $sku)->first()`. The `OrFail` variants, `findOrFail()` and `firstOrFail()`, throw `ModelNotFoundException` instead, which in an HTTP request Laravel renders as a 404. So I use the null-returning forms when a missing row is an expected case, such as a new SKU in an import, and the throwing forms when the row must exist and the caller cannot continue without it. `findOr()` takes a fallback closure, and `sole()` also throws when more than one row matches.
code
php · 12 lines<?php
use App\Models\Category;
use App\Models\Product;
$product = Product::firstWhere('sku', $row['sku']);
if ($product === null) {
$product = new Product(['sku' => $row['sku']]);
}
$category = Category::findOrFail($row['category_id']);go deeper
Recall which lookups return null and which throw ModelNotFoundException, and that find() uses the primary key.
Explain what each method adds to the query, how array ids change find() and findOrFail(), and when sole() is the stricter choice.
Choose null or exception by what absence means in the code path, and keep exception-driven lookups out of loops where missing rows are routine.
Set conventions for lookups across services so absence semantics are consistent between HTTP handlers, jobs and imports.
## The retrieval methods and their empty case Every Eloquent read starts from a **query builder** for the model, for example `Product::query()` or a static call such as `Product::where(...)`. The finishing method decides what you get back, and above all what you get when **no row matches**. That empty case is what interviewers probe, because it decides whether the calling code needs a null check or an exception handler. | Call | Match found | No match | |---|---|---| | `Product::find(42)` | the model | `null` | | `Product::findOrFail(42)` | the model | throws `ModelNotFoundException` | | `Product::where('sku', $sku)->first()` | the first model | `null` | | `Product::firstWhere('sku', $sku)` | the first model | `null` | | `Product::where('sku', $sku)->firstOrFail()` | the first model | throws `ModelNotFoundException` | | `Product::where('sku', $sku)->sole()` | the only model | throws `ModelNotFoundException`; more than one row throws `MultipleRecordsFoundException` | | `Product::findOr(42, fn () => ...)` | the model | the closure's return value | ## What each one actually runs - `find($id)` adds a `where` on the model's primary key and calls `first()`. Passing an **array** of ids switches to `findMany()`, which returns a **Collection**; missing ids are simply absent from it. - `findOrFail($id)` calls `find()` and throws when the result is `null`. With an array of ids it throws if **any** id is missing, comparing the result count with the unique ids requested. - `first()` adds `limit 1` to whatever query you built and returns the first model or `null`. - `firstWhere('sku', $sku)` is shorthand for `where('sku', $sku)->first()`; it accepts the same column, operator and value arguments as `where()`. - `sole()` fetches at most two rows so it can tell "none" from "more than one", which makes it useful when a lookup must be unique. ## Choosing between null and an exception The rule of thumb is to match the method to what an empty result **means**: 1. **Missing is normal** — use `find()` / `first()` / `firstWhere()` and branch on `null`. In a nightly electronics-feed import, a SKU that is not in the catalogue yet is expected, so the importer creates it. 2. **Missing is an error the caller cannot fix** — use `findOrFail()` / `firstOrFail()`. The exception stops the code path cleanly; in an HTTP request, Laravel's exception handler turns an uncaught `ModelNotFoundException` into a 404 response. 3. **Missing needs a custom fallback** — use `findOr()` with a closure, which runs only when nothing is found. 4. **More than one row would be a data bug** — use `sole()` so the bug surfaces instead of silently picking one row. ## A feed-import example ```php $product = Product::firstWhere('sku', $row['sku']); if ($product === null) { // new SKU in tonight's feed } $category = Category::findOrFail($row['category_id']); ``` The first lookup treats absence as ordinary; the second treats an unknown category id as a broken feed row that should abort the item. ## Common mistakes - Chaining a method on the result of `find()` without a null check, which fails with "call to a member function on null" as soon as an id is missing. - Wrapping `findOrFail()` in `try/catch` just to return `null`, which rebuilds `find()` with extra steps. - Writing `Product::where('id', $id)->first()` where `find($id)` states the intent and respects a custom primary key name. - Expecting `find([1, 2, 3])` to return `null` for a missing id; it returns a Collection without that id. - Using `first()` on a query that should match exactly one row, which hides duplicates that `sole()` would report. ## The rest of the family The same null-or-throw pattern repeats across a few less common helpers, and naming them shows you know the API rather than two methods of it: - `findMany([...])` is the explicit form of `find()` with an array and always returns a Collection, empty when nothing matches. - `findOrNew($id)` returns the found model or a **new, unsaved** instance, never `null`. - `findSole($id)` is `sole()` scoped to one key, throwing for none or several. - `firstOr(fn () => ...)` is `findOr()` for an arbitrary query: the closure runs only when `first()` finds nothing. None of these adds a new query shape; each is a thin wrapper that decides what an empty result becomes.
- What does `Product::findOrFail([1, 2, 3])` do if only ids 1 and 3 exist?It throws `ModelNotFoundException`. With an array, `findOrFail()` compares the number of models returned with the number of unique ids requested and throws when they differ, recording the missing ids on the exception. Plain `find([1, 2, 3])` would instead return a Collection holding the two models it found.
- When would you use `sole()` instead of `first()` on an Eloquent query?When exactly one row must match and a second one would be a data bug, for example looking up a product by a supplier code that should be unique. `sole()` fetches up to two rows and throws `ModelNotFoundException` for none or `MultipleRecordsFoundException` for more than one, while `first()` would silently return whichever row came first.
saying these in an interview costs you the question
- find() throws an exception when the id does not exist
- firstWhere() throws when no row matches
- find([1, 2, 3]) returns null if one of the ids is missing
- findOrFail() returns false when nothing is found
- Catching ModelNotFoundException to return null is the idiomatic lookup