25 REST API Interview Questions for 2026

·20 min read
By ·Updated
rest-apiinterview-questionshttpbackendapi-designauthentication

REST API interviews test whether candidates understand HTTP semantics rather than merely naming CRUD routes. Strong answers distinguish resources from representations, safe from idempotent methods, authentication from authorization, and transport retries from application deduplication.

Table of Contents

  1. REST Fundamentals Questions
  2. HTTP Methods Questions
  3. URL Design Questions
  4. HTTP Status Codes Questions
  5. Authentication Questions
  6. Pagination and Filtering Questions
  7. API Versioning Questions
  8. Error Handling Questions
  9. API Security Questions
  10. Quick Reference

REST Fundamentals Questions

These questions test your understanding of REST architecture and principles.

What is REST and what makes an API RESTful?

REST stands for Representational State Transfer. It is an architectural style described by constraints, not a synonym for JSON endpoints or CRUD. On HTTP, resource identifiers, representations, method semantics, media types, caching, and hypermedia form the interface between client and server.

A RESTful API should be:

  • Stateless - Each request carries the context needed for the server to understand it; client session state is not stored between requests
  • Resource-based - URLs represent things (nouns), not actions
  • Uniform interface - Resource identification, manipulation through representations, self-descriptive messages, and hypermedia-driven application state
  • Cacheable - Responses indicate if they can be cached

For example, for a users resource:

  • GET /users - List all users
  • GET /users/123 - Get specific user
  • POST /users - Create new user
  • PUT /users/123 - Replace user
  • PATCH /users/123 - Update user partially
  • DELETE /users/123 - Delete user

Proper status codes tell clients what happened: 200s for success, 400s for client errors, 500s for server errors.

What is HATEOAS and why is it important?

HATEOAS (Hypermedia As The Engine Of Application State) means clients progress through application states using controls supplied by representations. Links alone do not make an API self-documenting: clients still need media-type and link-relation semantics, but they do not need every transition URI hardcoded out of band.

In practice, few APIs fully implement it, but including pagination links and resource URLs is a good start:

// HATEOAS example
{
  "id": 123,
  "name": "John",
  "email": "john@example.com",
  "_links": {
    "self": { "href": "/api/users/123" },
    "posts": { "href": "/api/users/123/posts" },
    "update": { "href": "/api/users/123", "method": "PATCH" },
    "delete": { "href": "/api/users/123", "method": "DELETE" }
  }
}

HTTP Methods Questions

These questions test your knowledge of HTTP verbs and their proper usage.

What are the five main HTTP methods and their purposes?

Each HTTP method has a specific purpose and behavior characteristics:

MethodPurposeSafeIdempotentCacheable
GETRetrieve resource(s)YesYesYes
POSTProcess content with resource-specific semanticsNoNoPossible with explicit freshness and a reusable cache key
PUTCreate or replace target stateNoYesNo
PATCHApply a patch documentNoDepends on patch semanticsOnly under explicit PATCH caching conditions
DELETERemove resourceNoYesNo

What is the difference between PUT and PATCH?

PUT asks the server to create or replace the target resource's state with the enclosed representation. Which omitted fields disappear depends on that representation's contract. PUT is idempotent in its intended effect.

PATCH applies a patch document, so “only supplied fields change” describes JSON Merge Patch but not every PATCH media type. JSON Patch expresses operations such as add, remove, and test; a patch can be idempotent, but PATCH is not idempotent by definition. Advertise supported formats with Accept-Patch and send the matching Content-Type.

How do ETags and conditional requests improve APIs?

An ETag is a validator for a selected representation. A cache can send If-None-Match; if the validator still matches, the server responds 304 Not Modified without a response body, saving transfer while preserving HTTP cache semantics.

GET /users/123
If-None-Match: "user-123-v7"
 
HTTP/1.1 304 Not Modified
ETag: "user-123-v7"

Validators also prevent lost updates. A client reads a resource and later sends its validator in If-Match with PUT, PATCH, or DELETE. If another writer changed the resource, the precondition fails—typically with 412 Precondition Failed—instead of silently overwriting the newer state.

PATCH /users/123
If-Match: "user-123-v7"
Content-Type: application/merge-patch+json
 
{"name":"Jane"}

Strong and weak ETags have different comparison rules. Use a strong validator for If-Match, define Cache-Control explicitly for private or sensitive data, and include Vary when representation selection depends on request headers.

How do you implement CRUD operations with HTTP methods?

Each HTTP method maps to a specific database operation. Here's how to implement them properly with correct status codes:

// GET - Retrieve users
// GET /api/users
// GET /api/users/123
app.get('/api/users/:id?', async (req, res) => {
  if (req.params.id) {
    const user = await User.findById(req.params.id);
    if (!user) return res.status(404).json({ error: 'User not found' });
    return res.json(user);
  }
  const users = await User.find();
  res.json(users);
});
 
// POST - Create user
// POST /api/users
// Body: { "name": "John", "email": "john@example.com" }
app.post('/api/users', async (req, res) => {
  const { name, email } = req.body;
 
  if (!name || !email) {
    return res.status(400).json({ error: 'Name and email required' });
  }
 
  const existing = await User.findOne({ email });
  if (existing) {
    return res.status(409).json({ error: 'Email already exists' });
  }
 
  const user = await User.create({ name, email });
  res.status(201).json(user);  // 201 Created
});
 
// PUT - Replace entire user
// PUT /api/users/123
// Body: { "name": "John", "email": "john@example.com", "role": "admin" }
app.put('/api/users/:id', async (req, res) => {
  const { name, email, role } = req.body;
 
  // This API's PUT representation contract requires these fields.
  if (!name || !email) {
    return res.status(400).json({ error: 'All fields required for PUT' });
  }
 
  const user = await User.findByIdAndUpdate(
    req.params.id,
    { name, email, role },  // Complete replacement
    { new: true, overwrite: true }
  );
 
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json(user);
});
 
// PATCH - Partial update
// PATCH /api/users/123
// Body: { "email": "newemail@example.com" }
app.patch('/api/users/:id', async (req, res) => {
  // PATCH only updates provided fields
  const user = await User.findByIdAndUpdate(
    req.params.id,
    req.body,  // Only update fields in body
    { new: true }
  );
 
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json(user);
});
 
// DELETE - Remove user
// DELETE /api/users/123
app.delete('/api/users/:id', async (req, res) => {
  const user = await User.findByIdAndDelete(req.params.id);
 
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.status(204).send();  // 204 No Content
});

URL Design Questions

These questions test your understanding of RESTful resource naming.

What is wrong with verb-based URL design?

REST uses HTTP methods for actions, so URLs should contain nouns (resources), not verbs. Common anti-patterns include using action words in URLs:

# ❌ Bad URL Design
GET  /getAllUsers
POST /createNewPost
GET  /getPostComments?postId=123
POST /users/123/addComment
DELETE /removeComment/456

Problems:

  • Verbs in URLs (REST uses HTTP methods for actions)
  • Inconsistent naming
  • Actions mixed with resources

How do you design RESTful endpoints for a blog API?

RESTful design uses nouns for URLs and HTTP methods for actions. Here's a properly designed blog API:

# Users
GET    /users              # List all users
GET    /users/123          # Get user 123
POST   /users              # Create user
PUT    /users/123          # Replace user 123
PATCH  /users/123          # Update user 123
DELETE /users/123          # Delete user 123

# Posts
GET    /posts              # List all posts
GET    /posts?author=123   # Filter posts by author
GET    /posts/456          # Get post 456
POST   /posts              # Create post
PUT    /posts/456          # Replace post
PATCH  /posts/456          # Update post
DELETE /posts/456          # Delete post

# Comments (nested under posts)
GET    /posts/456/comments       # Comments on post 456
GET    /posts/456/comments/789   # Specific comment
POST   /posts/456/comments       # Add comment to post
DELETE /posts/456/comments/789   # Delete comment

# User's posts (alternative access pattern)
GET    /users/123/posts    # Posts by user 123

Design principles:

  • Nouns only in URLs
  • Plural resource names
  • Hierarchical relationships via nesting
  • Query parameters for filtering/sorting
  • Consistent patterns throughout

HTTP Status Codes Questions

These questions test your knowledge of proper status code usage.

What are the most important success status codes (2xx)?

Success codes indicate the request was received and processed successfully:

// 200 OK - General success
res.status(200).json(data);
 
// 201 Created - Include the new resource URI when available
res.location(`/users/${newResource.id}`).status(201).json(newResource);
 
// 204 No Content - Success but no body (DELETE, some PUTs)
res.status(204).send();

What client error codes (4xx) should you know?

Client error codes indicate problems with the request that the client should fix:

// 400 Bad Request - Malformed request, invalid syntax
res.status(400).json({ error: 'Invalid JSON in request body' });
 
// 401 Unauthorized - Include an applicable authentication challenge
res.set('WWW-Authenticate', 'Bearer realm="api"')
  .status(401)
  .json({ error: 'Authentication required' });
 
// 403 Forbidden - Authenticated but not authorized
res.status(403).json({ error: 'You cannot delete other users\' posts' });
 
// 404 Not Found - Resource doesn't exist
res.status(404).json({ error: 'User not found' });
 
// 409 Conflict - Resource state conflict
res.status(409).json({ error: 'Email already registered' });
 
// 422 Unprocessable Content - Syntactically valid, semantically invalid
res.status(422).json({
  error: 'Validation failed',
  details: [
    { field: 'email', message: 'Invalid email format' },
    { field: 'age', message: 'Must be at least 18' }
  ]
});
 
// 429 Too Many Requests - Rate limited
res.status(429).json({
  error: 'Rate limit exceeded',
  retryAfter: 60
});

For 429 and temporary 503 responses, send Retry-After when the server can provide a useful delay. Authorization policies sometimes return 404 instead of 403 to avoid disclosing that a protected resource exists.

What server error codes (5xx) should you know?

Server error codes indicate problems on the server side:

// 500 Internal Server Error - Unexpected server error
res.status(500).json({ error: 'Internal server error' });
 
// 503 Service Unavailable - Server overloaded/maintenance
res.status(503).json({
  error: 'Service temporarily unavailable',
  retryAfter: 300
});

When should you use each status code?

ScenarioStatus Code
GET succeeds200
POST creates resource201
DELETE succeeds204
Invalid request format400
Missing/invalid auth token401
Valid auth but no permission403
Resource doesn't exist404
Duplicate entry409
Validation errors422
Server crashed500

Authentication Questions

These questions test your understanding of API security and identity verification.

What is the difference between authentication and authorization?

Authentication verifies WHO you are—proving your identity through credentials like username/password, API keys, or tokens. Authorization determines WHAT you can do—checking if the authenticated user has permission to perform the requested action.

Authentication comes first (401 if failed), then authorization (403 if failed). Example: Logging in authenticates you; checking if you can delete a post authorizes the action.

How do API keys work for authentication?

API keys are bearer credentials commonly used to identify a calling application or project. They are not automatically user authentication and do not replace authorization. Send them only over TLS, keep them out of URLs and logs, store hashes where practical, scope and rotate them, and rate-limit by credential and principal.

// Client: Pass API key in header
const response = await fetch('/api/data', {
  headers: {
    'X-API-Key': 'your-api-key-here'
  }
});
 
// Server: Validate API key
app.use('/api', (req, res, next) => {
  const apiKey = req.headers['x-api-key'];
 
  if (!apiKey || !isValidApiKey(apiKey)) {
    return res.status(401).json({ error: 'Invalid API key' });
  }
 
  next();
});

How does JWT authentication work?

JWT is a signed (and optionally encrypted) token format, not an authentication method by itself. A deployment can validate a self-contained access token without a database lookup, but revocation, refresh-token rotation, logout, and risk controls may still require server-side state.

// Client: Login to get token
const response = await fetch('/api/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, password })
});
const { token } = await response.json();
 
// Client: Use token for subsequent requests
const data = await fetch('/api/users/me', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
 
// Server: Verify token
const jwt = require('jsonwebtoken');
 
function authMiddleware(req, res, next) {
  const authHeader = req.headers.authorization;
 
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }
 
  const token = authHeader.split(' ')[1];
 
  try {
    const decoded = jwt.verify(token, verificationKey, {
      algorithms: ['RS256'],
      issuer: 'https://issuer.example',
      audience: 'https://api.example'
    });
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

Validate the permitted algorithm, signature, issuer, audience, expiry, and application-required claims. Keep access tokens short-lived and avoid browser storage choices that expose bearer tokens to injected scripts; cookie-based credentials additionally require CSRF defenses.

How does OAuth 2.0 work?

OAuth 2.0 is an authorization framework for delegated access; OpenID Connect adds an identity layer. Current browser/native-client guidance uses the Authorization Code flow with PKCE, exact redirect URI matching, and a state value bound to the user-agent session. Public clients do not have a secret they can safely keep.

// Simplified shape; use a maintained OAuth/OIDC client library.
const { verifier, challenge } = createPkcePair();
const state = crypto.randomUUID();
await saveAuthorizationAttempt({ state, verifier, returnTo });
 
redirectToAuthorizationServer({
  response_type: 'code',
  client_id: CLIENT_ID,
  redirect_uri: EXACT_REDIRECT_URI,
  scope: 'openid profile email',
  state,
  code_challenge: challenge,
  code_challenge_method: 'S256'
});
 
// Callback: compare state in constant time, load and consume the verifier,
// then exchange code + verifier at the token endpoint over TLS.

Do not use the implicit grant for new clients. Validate authorization-server metadata, token issuer/audience, and OIDC nonce where applicable; rotate or sender-constrain refresh tokens and request the minimum scopes needed.


Pagination and Filtering Questions

These questions test your knowledge of handling large datasets in APIs.

How do you implement pagination in a REST API?

Pagination prevents returning thousands of records in a single response. Use query parameters for page number and limit, and include metadata in the response:

// GET /api/posts?page=2&limit=20
 
app.get('/api/posts', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const skip = (page - 1) * limit;
 
  const [posts, total] = await Promise.all([
    Post.find().skip(skip).limit(limit),
    Post.countDocuments()
  ]);
 
  res.json({
    data: posts,
    pagination: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit),
      hasNext: page * limit < total,
      hasPrev: page > 1
    }
  });
});

How do you implement filtering and sorting?

Use query parameters for filtering and sorting. A common convention is using a - prefix for descending order:

// GET /api/posts?status=published&author=123&sort=-createdAt,title
 
app.get('/api/posts', async (req, res) => {
  const { status, author, sort, search } = req.query;
 
  // Build filter
  const filter = {};
  if (status) filter.status = status;
  if (author) filter.author = author;
  if (search) filter.title = { $regex: search, $options: 'i' };
 
  // Build sort (- prefix = descending)
  let sortObj = {};
  if (sort) {
    sort.split(',').forEach(field => {
      if (field.startsWith('-')) {
        sortObj[field.slice(1)] = -1;
      } else {
        sortObj[field] = 1;
      }
    });
  }
 
  const posts = await Post.find(filter).sort(sortObj);
  res.json(posts);
});

API Versioning Questions

These questions test your understanding of API evolution and backward compatibility.

What are the different approaches to API versioning?

There are three main approaches to API versioning, each with trade-offs:

  1. URL path versioning (/api/v1/users) - Explicit and operationally simple, but changes resource URIs
  2. Query parameter (/api/users?version=1) - Flexible but easy to miss
  3. Media-type negotiation (Accept: application/vnd.example.user-v2+json) - Keeps the URI stable but adds negotiation and cache-key complexity

Prefer compatible evolution when clients can ignore added fields. Introduce a new contract for breaking semantic or representation changes, document migration, measure remaining consumers, and communicate lifecycle with the standardized Deprecation response field, a Link to migration guidance, and Sunset when a retirement date is planned.

How do you implement URL path versioning?

URL path versioning is explicit and straightforward to route. Mount different routers for each version:

// /api/v1/users
// /api/v2/users
 
const v1Router = require('./routes/v1');
const v2Router = require('./routes/v2');
 
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

How do you implement header versioning?

Header versioning keeps URLs clean but is harder to test in browsers:

// Accept: application/vnd.myapi.v1+json
 
app.use('/api', (req, res, next) => {
  const accept = req.headers.accept || '';
  const match = accept.match(/application\/vnd\.myapi\.v(\d+)\+json/);
 
  req.apiVersion = match ? parseInt(match[1]) : 1;
  next();
});
 
app.get('/api/users', (req, res) => {
  if (req.apiVersion === 2) {
    // V2 response format
    return res.json({ data: users, meta: { count: users.length } });
  }
  // V1 response format
  res.json(users);
});

Error Handling Questions

These questions test your approach to API error responses and consistency.

How do you handle errors in a REST API?

Use the most specific HTTP status whose semantics fit, and keep error bodies stable and machine-readable. RFC 9457 defines application/problem+json with type, title, status, detail, and instance, plus extension members for domain fields. Do not expose stack traces, SQL, secrets, or internals.

// RFC 9457-style problem details
{
  "type": "https://api.example/problems/validation",
  "title": "Request validation failed",
  "status": 422,
  "detail": "One or more fields are invalid.",
  "instance": "/problems/01J...",
  "errors": [{ "field": "email", "code": "invalid_format" }]
}
 
// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err);
 
  if (err.name === 'ValidationError') {
    return res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: err.message,
        details: Object.values(err.errors).map(e => ({
          field: e.path,
          message: e.message
        }))
      }
    });
  }
 
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred'
    }
  });
});

What is the difference between REST and GraphQL?

HTTP APIs in a REST style expose resource representations and standard method semantics, often across multiple URIs. GraphQL exposes a typed schema where clients select fields and compose operations, commonly over one HTTP endpoint.

Neither is universally simpler or faster. HTTP caching maps naturally to GET resource URIs, while GraphQL commonly needs application-aware caching and query-cost controls. GraphQL can reduce over-fetching but can still cause server-side N+1 work; REST can offer sparse fields, embedding, and batch resources. Choose based on contract evolution, client diversity, caching, observability, authorization, and operational skills.


API Security Questions

These questions test your understanding of securing APIs in production.

How do you secure a REST API?

API security requires multiple layers working together:

  1. TLS - Protect data in transit and validate certificates
  2. Authentication - Use a threat-modelled session, OAuth/OIDC, mTLS, or scoped service credential
  3. Authorization - Check permissions on every request
  4. Rate limiting - Prevent abuse
  5. Input validation - Never trust client data
  6. Browser controls - Configure CORS narrowly; remember it is not authentication and non-browser clients ignore it
  7. Abuse and failure controls - Rate limits, body/time limits, backpressure, audit logs, and safe errors
  8. Secrets and dependencies - Rotation, least privilege, patching, and tested incident response

How do you prevent duplicate resources from POST timeouts?

An idempotency key works only with a defined server contract. Scope it to the authenticated principal and operation, atomically reserve it with a fingerprint of the request, store the final status/headers/body for a retention window, and reject reuse with a different payload. Concurrent duplicates should wait for or observe the same operation rather than execute twice.

For creation at a client-chosen URI, an idempotent PUT with If-None-Match: * can express “create only if absent”. In both designs, downstream side effects need the same deduplication boundary or a transactional outbox; merely caching an HTTP response after processing is too late to prevent duplicate work.


Quick Reference

MethodPurposeIdempotentRequest Body
GETRetrieveYesNo
POSTResource-defined processing, often createNo by defaultYes
PUTCreate/replace target stateYesYes
PATCHApply patch documentDepends on patchYes
DELETERemoveYesOptional
StatusMeaningUse Case
200OKSuccessful GET/PUT/PATCH
201CreatedSuccessful POST
204No ContentSuccessful DELETE
400Bad RequestInvalid syntax
401UnauthorizedAuth required
403ForbiddenNo permission
404Not FoundResource missing
412Precondition FailedValidator such as If-Match failed
422Unprocessable ContentSemantically invalid content
429Too Many RequestsRate limit exceeded
500Server ErrorSomething broke

Official Sources


Frequently Asked Questions

What is REST and what makes an API RESTful?

REST is an architectural style defined by client-server separation, stateless requests, cache constraints, a uniform interface, layered systems, and optional code-on-demand. HTTP methods and resource URIs are common implementation mechanisms, but JSON over HTTP is not automatically REST. Hypermedia as the engine of application state is part of REST's uniform-interface constraint.

What is the difference between PUT and PATCH?

PUT requests that the target resource's state be created or replaced by the enclosed representation and is idempotent by HTTP semantics. PATCH applies a patch document whose semantics depend on its media type, such as JSON Patch or JSON Merge Patch; PATCH is not inherently idempotent, though a specific patch can be. Use conditional requests to prevent lost updates.

What are the most important HTTP status codes to know?

Key status codes include 200 OK, 201 Created (normally with Location), 202 Accepted for asynchronous processing, 204 No Content, 400 Bad Request, 401 Unauthorized with an authentication challenge, 403 Forbidden, 404 Not Found, 409 Conflict, 412 Precondition Failed, 422 Unprocessable Content, 429 Too Many Requests, 500 Internal Server Error, and 503 Service Unavailable. Choose by standardized semantics, not a memorized CRUD table.

What is the difference between authentication and authorization?

Authentication verifies WHO you are - proving your identity through credentials like username/password, API keys, or tokens. Authorization determines WHAT you can do - checking if the authenticated user has permission to perform the requested action. Authentication comes first (401 if failed), then authorization (403 if failed). Example: Logging in authenticates you; checking if you can delete a post authorizes the action.

How do you handle API versioning?

Common approaches are a version in the path, a query parameter, or media-type/content negotiation. Do not version automatically: preserve compatible evolution and introduce a new contract for breaking changes. Publish migration guidance and timelines; the standardized Deprecation response field and Sunset header can communicate lifecycle information.

What is idempotency and why does it matter in REST APIs?

A method is idempotent when multiple identical requests have the same intended server effect as one; responses may differ, such as DELETE returning 204 and then 404. Safe methods, PUT, and DELETE are idempotent by HTTP semantics. POST is not inherently idempotent but an API can add idempotency-key semantics. Retries still need backoff, deadlines, and awareness of application side effects.

Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides