0

Status codes — 2xx, 3xx, 4xx, 5xx — the 15 you must know cold

Beginner5 min read·eng-13-006
interview

Concept

HTTP status codes — a 3-digit number in every response that tells the client what happened. The first digit defines the class.

Classes:

  • 1xx Informational: Request received, processing continues. Rare.
  • 2xx Success: Request was received, understood, and accepted.
  • 3xx Redirection: Further action needed to complete the request.
  • 4xx Client Error: The request is wrong. Client should fix it.
  • 5xx Server Error: The server failed to process a valid request.

The 15 you must know:

2xx:

  • 200 OK: Standard success. Response body contains the result.
  • 201 Created: Resource was created. Include a Location header with the new resource URL.
  • 204 No Content: Success, but no body. Use for DELETE and actions that don't return data.
  • 206 Partial Content: Range request succeeded. Used for video streaming, resumable downloads.

3xx:

  • 301 Moved Permanently: URL changed forever. Search engines update their index.
  • 302 Found: Temporary redirect. "Found" is a misnomer — it means "go here for now."
  • 304 Not Modified: Client's cached version is still valid. No body sent.

4xx:

  • 400 Bad Request: Malformed request syntax, invalid parameters. Generic client error.
  • 401 Unauthorized: Not authenticated. Misleading name — it means "not logged in."
  • 403 Forbidden: Authenticated but not authorized. "I know who you are, you just can't do this."
  • 404 Not Found: Resource doesn't exist.
  • 405 Method Not Allowed: The URL is valid, but not that HTTP method.
  • 422 Unprocessable Entity: Validation failed. Syntactically correct request, semantically wrong.
  • 429 Too Many Requests: Rate limited.

5xx:

  • 500 Internal Server Error: Generic server crash. Something unexpected happened.
  • 503 Service Unavailable: Server is overloaded or down for maintenance.

Code Example

php
<?php
// Laravel responses with correct status codes

// 200 OK — default, reading data
Route::get('/users/{id}', function (int $id) {
    return response()->json(User::findOrFail($id)); // 200 by default
});

// 201 Created — resource creation
Route::post('/users', function (CreateUserRequest $request) {
    $user = User::create($request->validated());
    return response()
        ->json($user, 201)
        ->header('Location', route('users.show', $user));
});

// 204 No Content — success with no body
Route::delete('/users/{id}', function (int $id) {
    User::findOrFail($id)->delete();
    return response()->noContent(); // 204
});

// 301 Permanent redirect
Route::get('/old-path', fn() => redirect('/new-path', 301));

// 304 Not Modified — conditional GET with ETag
Route::get('/users/{id}', function (int $id, Request $request) {
    $user = User::findOrFail($id);
    $etag = md5($user->updated_at->timestamp);
    if ($request->header('If-None-Match') === "\"{$etag}\"") {
        return response('', 304); // client's cache is valid — no body
    }
    return response()->json($user)->header('ETag', "\"{$etag}\"");
});

// 400 Bad Request
Route::post('/search', function (Request $request) {
    if (!$request->has('q')) {
        return response()->json(['error' => 'Missing required parameter: q'], 400);
    }
});

// 401 vs 403 — critical distinction
Route::get('/admin', function () {
    if (!auth()->check()) {
        return response()->json(['error' => 'Authentication required'], 401); // not logged in
    }
    if (!auth()->user()->isAdmin()) {
        return response()->json(['error' => 'Insufficient permissions'], 403); // logged in, no access
    }
    return AdminResource::collection(User::all());
});

// 422 Unprocessable — validation errors
// Laravel's FormRequest throws this automatically
// Or manually:
Route::post('/orders', function (Request $request) {
    $validated = validator($request->all(), ['amount' => 'required|numeric|min:1'])->validate();
    // If validation fails, Laravel returns 422 with errors automatically
});

// 429 Too Many Requests
Route::middleware('throttle:60,1')->group(function () {
    // 60 requests per 1 minute — 429 when exceeded
    Route::get('/api/data', fn() => response()->json(['data' => 'ok']));
});

// 500 — don't return this intentionally; it means an unhandled exception
// In Handler.php, 5xx responses are logged and monitored

// 503 — maintenance mode
// php artisan down → returns 503 for all requests