Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Interview questions›REST API Design

144 REST API Design interview questions and answers

Grouped the way the course is: foundations first, advanced last. Every answer is written out in full.

Learn REST API DesignTake the quiz

On this page

  • HTTP Fundamentals
  • REST Principles
  • Request Handling
  • Security
  • Documentation
  • Testing
  • Interview Prep

HTTP Fundamentals

HTTP Methods — GET, POST, PUT, PATCH, DELETE

What is the primary purpose of the GET method in a RESTful API design?

The GET method is used strictly to retrieve data from a server. In a RESTful architecture, it is defined as a safe and idempotent method, meaning it should never change the state of the resource it accesses. When a client performs a GET request, the server should return the requested resource representation, typically in JSON format, without performing any side effects on the backend database or resource stores.

How does the POST method differ from GET, and when should it be used?

Unlike GET, which is safe, the POST method is used to send data to the server to create a new resource. It is neither safe nor idempotent because calling it multiple times will typically result in the creation of multiple duplicate resources. For example, a POST request to '/api/users' adds a new user entry. The server returns a 201 Created status code to confirm that the new resource has been successfully instantiated.

Can you explain the difference between the PUT and PATCH methods when updating resources?

The PUT method is designed for full resource replacement. If you send a PUT request to update a user, you must provide the complete object, and the server replaces the existing resource entirely with the new payload. In contrast, the PATCH method is used for partial updates. You only send the specific fields that need to change, such as {'email': 'new@example.com'}, which is much more efficient for bandwidth and avoids accidental data loss during update operations.

What is the role of the DELETE method, and how should it behave in a REST API?

The DELETE method is used to remove a specific resource from the server. Once a client sends a DELETE request to a specific URL, such as '/api/orders/123', the server is expected to delete that resource. Upon successful deletion, the API should return a 200 OK or a 204 No Content status code. It is generally considered idempotent because deleting a resource that is already gone still results in the resource being absent.

Compare the use of PUT and POST when creating or updating resources in a design context.

The choice between PUT and POST depends on who assigns the resource identifier. With POST, the server typically decides the ID, which is common for resource collections. With PUT, the client usually specifies the URI of the resource, such as PUT '/api/products/item-001'. Use POST when you want the server to manage IDs to prevent collisions, and use PUT when the client has authority over the resource location, often used for idempotency-sensitive updates.

Why is the concept of idempotency so critical in REST API design, and which HTTP methods demonstrate it?

Idempotency ensures that performing the same request multiple times yields the same result as performing it once, which is vital for reliability in distributed systems where network failures occur. GET, PUT, PATCH, and DELETE are considered idempotent, while POST is not. For example, if a client experiences a timeout during a PUT request and retries, the final state remains the same, preventing inconsistent data states in the backend database.

HTTP Status Codes — 2xx, 3xx, 4xx, 5xx

What does a 2xx status code generally signify in the context of a REST API design?

A 2xx status code indicates that the client's request was successfully received, understood, and accepted by the server. The most common codes are 200 OK, which signifies a standard successful response, and 201 Created, which is used when a resource has been successfully instantiated, usually via a POST method. By returning 2xx codes, the API provides a clear, machine-readable signal that the operation concluded without errors, allowing the client to proceed with processing the returned payload or confirm that the state transition occurred as expected.

Explain the purpose of 3xx status codes and provide a common use case in REST API architecture.

3xx status codes represent redirection, informing the client that further action is required to fulfill the request. In REST API design, the most critical code is 301 Moved Permanently or 307 Temporary Redirect. For example, if you move a resource from '/v1/users' to '/v2/users', returning a 301 informs the client to update their implementation. This ensures API longevity and backward compatibility, as it allows servers to transition endpoints while gracefully guiding clients to the new, correct URI location without causing a breaking change for existing integrations.

When should an API return a 4xx status code, and how does this differ from 5xx codes?

4xx status codes signify client-side errors, meaning the request contains bad syntax or cannot be fulfilled due to issues like unauthorized access (401) or forbidden resources (403). Conversely, 5xx codes indicate server-side errors, where the server failed to fulfill a seemingly valid request. The distinction is crucial for debugging: a 4xx error tells the developer to check their request parameters, headers, or authentication tokens, whereas a 5xx error indicates that the server is malfunctioning, overloaded, or experiencing an unexpected internal exception that requires intervention from the system administrators.

Compare the use of 404 Not Found versus 410 Gone in a REST API. When would you prefer one over the other?

Both 404 and 410 signal that a resource is unavailable, but they carry different semantic meanings. A 404 Not Found is the general-purpose response when a resource does not exist at the requested URI. You should prefer 410 Gone when you intentionally removed a resource and want to inform the client that it will never return. Using 410 is a superior design practice because it signals to client-side caching mechanisms and search engines that they should stop requesting that specific URI, effectively reducing unnecessary traffic and helping clients clean up their internal references.

Why is it important to distinguish between 401 Unauthorized and 403 Forbidden in a REST API design?

Distinguishing between 401 and 403 is essential for proper security implementation. A 401 Unauthorized status means the request lacks valid authentication credentials; it acts as a challenge, signaling the client to provide valid credentials like an Authorization header. In contrast, 403 Forbidden means the server understands the authentication but refuses to authorize the request because the authenticated user does not have sufficient permissions to perform the specific action on the requested resource. This distinction allows clients to present appropriate recovery flows, such as prompting for login (401) versus informing the user they lack the required organizational role (403).

When implementing a resource update in a REST API, how should you handle concurrent modifications using HTTP status codes?

To handle concurrent modifications, you should utilize the 412 Precondition Failed status code in conjunction with ETag headers. In a typical flow, the client performs a GET and receives an ETag representing the current state. When the client attempts a PUT update, it includes the 'If-Match' header. If another client has updated the resource in the interim, the server will detect the mismatched ETag and return 412. This approach ensures data consistency and prevents the 'lost update' problem, forcing the client to fetch the latest state and reconcile their changes before proceeding with the update.

Request and Response Headers

What is the primary purpose of Request and Response headers in a REST API design?

Request and Response headers act as metadata containers that provide essential context about the data being exchanged between a client and a server. They serve as a communication bridge, allowing the client to specify preferences like content formats via 'Accept' or authentication credentials via 'Authorization'. Simultaneously, headers allow the server to describe the nature of the response, such as 'Content-Type' or caching directives. This decoupling of metadata from the actual payload ensures that both parties can process the interaction correctly without needing to parse the body first, which is critical for scalable, efficient API architectures.

How does the 'Content-Type' header differ from the 'Accept' header, and why is this distinction important?

The 'Content-Type' header is used in both requests and responses to inform the recipient about the media type of the actual body being sent, such as 'application/json'. Conversely, the 'Accept' header is sent only by the client to tell the server which media types it is capable of understanding. This distinction is vital for content negotiation. For instance, if a client sends 'Accept: application/xml', the server knows to serialize the resource into XML. Without these two headers working in tandem, the server could not dynamically adapt its responses to suit various client requirements, significantly reducing the API's flexibility.

Can you explain the role of 'Cache-Control' headers and how they contribute to API performance?

The 'Cache-Control' header is the primary mechanism for managing resource freshness and defining how long a response can be stored by intermediate proxies or the client browser. By setting directives like 'max-age=3600', a server instructs the client to reuse a cached copy for one hour, which drastically reduces latency and server load. This is essential for REST APIs, as it prevents unnecessary re-fetching of static or infrequently changing resources. Efficient caching strategies reduce bandwidth consumption and improve perceived response times for the end user, which are core pillars of high-performance API design.

Compare the use of 'Authorization' headers versus 'Set-Cookie' for managing authentication in a RESTful environment.

The 'Authorization' header is the preferred RESTful standard, typically carrying a Bearer token. It is stateless and explicit, which aligns perfectly with the REST principle of keeping interactions independent. In contrast, 'Set-Cookie' relies on stateful sessions managed by the server, often requiring the browser to automatically manage cookies, which makes it harder to use in mobile or cross-domain contexts. I recommend the 'Authorization' header because it is easier to debug, inherently works better with stateless architectures, and does not depend on the browser's implicit state-handling mechanisms, providing a much more predictable and secure authentication flow for modern APIs.

What is the function of 'ETag' and 'If-None-Match' headers, and how do they facilitate conditional requests?

The 'ETag' is a unique identifier—often a hash—representing a specific version of a resource. When a client performs a GET request, the server returns the 'ETag'. If the client requests that resource again, it sends the 'If-None-Match' header containing that ETag. The server compares this to the current resource version. If they match, the server returns a '304 Not Modified' status without a body. This pattern saves significant bandwidth and processing power, as it avoids re-transferring large resources that haven't actually changed, ensuring that client-side data stays synchronized with the server efficiently.

How would you design a rate-limiting strategy using headers, and what information should be conveyed to the client?

To implement rate limiting, I would utilize custom headers like 'X-RateLimit-Limit', 'X-RateLimit-Remaining', and 'X-RateLimit-Reset'. These headers provide transparency to the client regarding their current usage. When the limit is exceeded, I would return a '429 Too Many Requests' status code along with a 'Retry-After' header. This header is crucial because it informs the client exactly how many seconds to wait before attempting another request. By providing this metadata, the API becomes a 'good citizen,' allowing the client to build adaptive back-off logic rather than simply crashing or constantly retrying blindly, which leads to a more robust system.

HTTP/2 and HTTP/3 Overview

What is the fundamental improvement HTTP/2 introduced for REST API performance compared to HTTP/1.1?

The primary improvement is multiplexing over a single TCP connection. In HTTP/1.1, the 'head-of-line blocking' issue occurred because browsers limited the number of concurrent connections to a server, and each request had to wait for the previous one to complete. HTTP/2 allows multiple request and response streams to be active simultaneously on one connection, which drastically reduces latency and overhead, making RESTful resource fetching significantly faster for complex API architectures.

How does Header Compression (HPACK) in HTTP/2 benefit REST API design?

REST APIs often send repetitive headers, such as 'Authorization', 'Content-Type', and custom API keys, with every single request. HTTP/2 uses HPACK to compress these headers using static and dynamic tables. This reduces the size of the request and response payloads, minimizing the total number of bytes sent over the network. For mobile clients or high-traffic REST services, this significantly saves bandwidth and decreases the time required to establish context for each API interaction.

Can you explain the purpose of HTTP/3 and why it shifts away from TCP?

HTTP/3 is built on top of QUIC, a transport layer protocol that uses UDP instead of TCP. The reason for this transition is to solve 'TCP head-of-line blocking.' In TCP, if a single packet is lost, all subsequent packets must wait for retransmission, even if they belong to different data streams. QUIC eliminates this bottleneck because packet loss in one stream does not impact the others, ensuring more reliable and consistent performance for REST APIs operating over unstable or high-latency wireless networks.

Compare the connection establishment process between HTTP/2 and HTTP/3. Why is this critical for REST API performance?

HTTP/2 requires a standard TCP three-way handshake followed by a TLS negotiation, which involves multiple round trips. In contrast, HTTP/3 (QUIC) combines the transport and cryptographic handshake into a single process. This '0-RTT' or '1-RTT' setup allows the client and server to start sending application data almost immediately. For REST APIs, this means users experience significantly lower 'time to first byte,' which is crucial for modern applications relying on microservices and frequent cross-service communication.

What is Server Push in HTTP/2, and when should it be avoided in REST API design?

Server Push allows a server to send resources to a client before the client explicitly requests them. For example, if a client requests an API endpoint for a 'User Profile', the server could preemptively push the user's 'Settings' and 'Permissions' resources. However, it should be used with extreme caution. If overused, it wastes bandwidth by sending data the client already has cached. In most REST API designs, it is better to rely on client-side requests or proper resource linking (HATEOAS) rather than forcing pushed data.

How does the shift from HTTP/2 to HTTP/3 affect error handling and connection migration for REST services?

HTTP/3 introduces 'connection migration' as a native feature. Because QUIC identifies connections using a unique Connection ID rather than a 4-tuple of IP addresses and ports, a client can switch from Wi-Fi to cellular data without dropping the connection. In HTTP/2, the connection is tied to the IP/port, causing a reset during network switching. For REST APIs, this provides a seamless experience for clients, preventing interrupted requests and unnecessary overhead from frequent re-authentications during transitions between different network interfaces.

REST Principles

REST Architectural Constraints

What is the client-server constraint in REST, and why is it important for API design?

The client-server constraint mandates a strict separation of concerns between the user interface and data storage. By decoupling the client from the server, we enable independent evolution of both components. This means the server can be updated, scaled, or migrated to different infrastructure without requiring changes to the client application. Furthermore, it improves portability across multiple platforms, such as mobile apps and web browsers, as they all interact with the same backend resource interface.

Can you explain the stateless constraint and the reasoning behind it?

The stateless constraint requires that every request from a client to a server must contain all the information necessary for the server to understand and process the request. The server does not store any session state about the client between requests. This is crucial for scalability, as it allows the server to handle requests from any client at any time without needing to coordinate session synchronization across multiple server nodes, which significantly simplifies the infrastructure.

What is the significance of the uniform interface constraint in RESTful services?

The uniform interface is the defining characteristic of REST that distinguishes it from other architectural styles. It simplifies the architecture by providing a standardized way to interact with resources, typically through HTTP verbs like GET, POST, PUT, and DELETE. By using standard methods, URIs, and resource representations, the API becomes self-descriptive and predictable. This consistency reduces the learning curve for developers and allows for easier integration between various clients and services.

How does the cacheable constraint improve the performance of a REST API?

The cacheable constraint requires that responses must explicitly define themselves as cacheable or non-cacheable. When a response is cacheable, the client or an intermediary like a proxy or CDN can reuse that data for later requests. This significantly reduces latency and lowers server load by eliminating redundant processing. By using HTTP headers like 'Cache-Control' or 'ETag', we provide clear instructions to the infrastructure, which improves user experience by delivering faster data retrieval for frequently accessed resources.

What is the HATEOAS constraint, and why is it often considered the most complex to implement?

HATEOAS, or Hypermedia as the Engine of Application State, means the server provides information dynamically through hypermedia links. Instead of the client needing to hardcode URIs, the server tells the client what actions are available next based on the current state. For example, a response might look like: {'id': 1, 'links': [{'rel': 'next', 'href': '/orders/2'}]}. It is complex because it requires tight coupling between the client's understanding of hypermedia formats and the server's sophisticated response generation, moving beyond simple CRUD operations.

Compare the 'Layered System' constraint with the 'Code on Demand' constraint regarding how they handle system flexibility.

The Layered System constraint promotes flexibility by allowing an architecture to be composed of hierarchical layers, such as load balancers, security filters, or caching proxies, without the client knowing it is communicating with a middleman. This hides complexity and improves security and scalability. Conversely, 'Code on Demand' offers flexibility by allowing the server to extend client functionality by transferring executable code, like scripts. While Layered Systems manage infrastructure complexity, Code on Demand pushes logic to the client, trading off security and maintainability for dynamic, runtime capability enhancement.

Resource Naming and URL Design

What is the fundamental difference between using plural nouns and singular nouns when designing REST resource URIs?

In REST API design, it is standard practice to use plural nouns for resource identifiers, such as '/users' instead of '/user'. The rationale is that a URI represents a collection of resources. When you access '/users', you are logically pointing to the entire set. If you need a specific item, you append the identifier, like '/users/123'. Using plurals provides consistency and predictability, which makes the API easier for developers to navigate, as they intuitively understand that a base path represents a category of data objects rather than a single instance.

Why should we avoid including verbs in our RESTful resource paths?

REST is a resource-oriented architecture, not a remote procedure call mechanism. Paths should represent the 'what'—the data entities—rather than the 'how'—the actions. We rely on the HTTP methods (GET, POST, PUT, DELETE) to define the operation. For example, use 'GET /orders' to retrieve orders instead of '/getOrders'. Including verbs leads to bloated, non-standard URIs that ignore the semantic power of the HTTP protocol. By focusing on nouns, you keep the URI space clean, intuitive, and properly aligned with REST principles, ensuring that clients interact with resources using standard HTTP verbs.

How do you handle hierarchical relationships in URIs, and what is the maximum depth one should aim for?

Hierarchical relationships are best represented by nesting child resources under parent resources, such as '/authors/5/books'. This structure clearly defines the ownership or association between data entities. However, you should generally keep URI depth to no more than two or three levels, like '/authors/5/books/10'. If you go deeper, the URIs become brittle, hard to read, and difficult to manage. If a resource has many relationships, it is often better to flatten the URI structure and use query parameters to filter by parent ID, keeping the path simple and maintainable.

Compare using query parameters versus sub-resources for filtering and sorting.

Sub-resources are best for defining specific data relationships, such as '/users/1/orders'. Conversely, query parameters are the correct tool for filtering, sorting, and pagination of an existing collection. For example, 'GET /users?status=active&sort=name' is far superior to creating custom paths like '/users/active/sorted'. Using query parameters keeps the resource URI stable and avoids 'path explosion,' where you would otherwise need countless endpoint variations. Always use sub-resources for identity and association, but reserve query parameters for the presentation and refinement of that data set.

What are the risks of exposing internal database IDs in your public-facing resource URIs, and what is a common alternative?

Exposing internal database primary keys, like auto-incrementing integers, in URIs can be a security risk. It allows malicious actors to guess valid resource IDs or estimate the volume of data in your system. To mitigate this, many designers use Universally Unique Identifiers (UUIDs) or URL-friendly slugs. For example, using '/products/a1b2c3d4-e5f6-...' instead of '/products/12' obscures the internal structure of your database. This approach decouples your public API contract from your internal data storage, allowing you to refactor your backend without breaking public links or leaking sensitive business intelligence about your record counts.

Explain the design strategy for an API that requires managing a singleton resource versus a collection.

A singleton resource represents exactly one instance that doesn't have a parent ID, such as a user profile. In this case, you might design the URI as '/profile'. For a collection, you use plural nouns like '/profiles'. The challenge arises in 'sub-singleton' cases, such as a user’s single settings object, which is accessed via '/users/1/settings'. The design strategy here is to maintain a consistent pattern: the URI must point to a stable resource identifier. If an object is unique to a context, omit the ID for that specific segment of the path to keep the API design clean and semantically accurate.

API Versioning Strategies

What is the primary purpose of versioning a REST API, and why is it essential for long-term maintenance?

The primary purpose of versioning is to enable evolution while maintaining backward compatibility. As systems grow, requirements change, necessitating structural shifts in data models or business logic. Without versioning, a breaking change would instantly crash all existing client integrations. Versioning allows developers to introduce new features or change response formats in a 'v2' endpoint while keeping the 'v1' endpoint stable, providing a controlled migration path for consumers, ensuring that legacy clients remain functional while newer clients adopt modern standards.

How does URI path versioning work, and what are its main advantages and disadvantages?

URI path versioning involves embedding the version number directly into the resource path, for example, 'https://api.example.com/v1/users'. The main advantage is visibility and simplicity; it is easily cached by infrastructure, and developers can immediately identify the API version by looking at the URL. However, the disadvantage is that it violates the REST principle that a URI should represent a unique resource. By changing the path to '/v2/', you are technically creating a new URI for the same resource, which can lead to redundancy in implementation.

Explain how versioning via Custom Headers works as an alternative to URL versioning.

Versioning via headers, such as using a 'X-API-Version' header, keeps the URI clean because the resource identifier remains consistent regardless of the version being requested. The client sends 'X-API-Version: 2' in the request, and the server routes the logic accordingly. This approach adheres better to the REST concept of resource identifiers. The downside is that it can make caching more complex, as the cache key must now include the custom header, and it is less visible to developers who are debugging or inspecting requests simply by viewing the URL in a browser.

Compare URI path versioning with Media Type versioning (Content Negotiation). Which is generally preferred in strict RESTful design?

URI path versioning is easier to implement and debug, but Media Type versioning, often called 'Content Negotiation', is arguably more 'RESTful'. In Media Type versioning, the client specifies the version in the 'Accept' header, like 'Accept: application/vnd.myapi.v2+json'. This allows the same URI to return different representations of the same resource. While Media Type versioning is powerful and avoids URI bloat, it is significantly harder to test and implement because it requires complex server-side logic to parse headers and negotiate the correct format, whereas URI versioning is straightforward and universally supported by all infrastructure tools.

When should you decide to release a new major version versus keeping the API backward compatible?

A new major version should be released only when breaking changes are unavoidable. Examples include removing fields from a JSON response, renaming required parameters, changing the authentication flow, or altering the semantics of an HTTP status code. If a change is additive—such as adding a new optional field—it should be implemented in the current version without incrementing the major version. Maintaining backward compatibility is always preferred because it prevents the high cost of forcing all consumers to update their client code immediately, which helps preserve the developer ecosystem and trust.

How would you implement a deprecation strategy for an older version of a REST API to minimize consumer frustration?

An effective deprecation strategy requires communication and technical markers. First, you should introduce the 'Sunset' and 'Deprecation' HTTP headers to inform clients that an endpoint will be removed at a specific future date. Additionally, you should include a warning field in the API response payload. You must provide clear documentation detailing the migration steps from the old version to the new one. Finally, monitor usage logs to identify clients that have not migrated, reaching out to them directly before the hard-shutdown date to ensure they have sufficient time to transition their infrastructure, preventing accidental production downtime.

Idempotency and Safety

What is the fundamental difference between a safe HTTP method and an idempotent HTTP method in REST API design?

A safe method is one that does not change the state of the resource on the server; it is essentially read-only, such as GET, HEAD, or OPTIONS. An idempotent method, on the other hand, means that making multiple identical requests has the same side effect on the server as making a single request. While safe methods are inherently idempotent, non-safe methods like PUT or DELETE must be explicitly designed to be idempotent to ensure predictable behavior during network retries.

Why is the GET method considered safe, and what risks occur if a developer violates this principle?

The GET method is considered safe because it is intended to retrieve a representation of a resource without triggering any state changes. When a developer violates this by performing actions like database updates or email triggers within a GET request, they break the expectations of HTTP intermediaries. Search engine crawlers or browser pre-fetching mechanisms may unexpectedly execute these side effects multiple times, leading to data corruption, inaccurate analytics, or unintended transactional outcomes for the end user.

Explain why PUT is defined as an idempotent method while POST is typically not.

PUT is idempotent because it is a 'replace' operation; sending the same request body to the same URI multiple times results in the resource being updated to the exact same state, effectively overwriting previous values. Conversely, POST is used to create new resources or trigger non-idempotent processes. Each POST request creates a unique entry, such as incrementing an order count or generating a new database ID, meaning repeat requests produce different, cumulative side effects on the server state.

Compare the use of PUT versus PATCH when updating resources, specifically regarding idempotency requirements.

PUT requires the client to send a complete representation of the resource, making it natively idempotent because the server simply replaces the existing state with the provided object. PATCH is intended for partial updates and is not strictly required to be idempotent by the HTTP specification. While PATCH can be designed to be idempotent using specific patch-sets, it is more prone to drift if the server-side logic applies delta operations that depend on the current resource state.

How can you implement idempotency for a POST request that does not support it natively?

To make a non-idempotent POST request idempotent, the standard approach is to use an 'Idempotency-Key' header. The client generates a unique identifier, such as a UUID, and includes it in the request. The server stores this key temporarily. If a subsequent request arrives with the same key, the server recognizes it as a duplicate and returns the original response instead of re-processing the logic, such as: if (cache.contains(key)) return cache.get(key); processRequest(); cache.save(key, response);

Discuss the trade-offs of using server-side idempotency keys versus designing your API to be inherently idempotent through resource-based URIs.

Relying on resource-based URIs is the preferred RESTful practice because it leverages HTTP semantics directly; for example, using a specific sub-resource URI for a transaction instead of a generic POST endpoint. This reduces coupling and side-effect complexity. However, idempotency keys are more flexible for complex business workflows where the client needs to ensure a single execution of a multi-step process without creating hundreds of intermediate resources. While keys add overhead to the server for storage and validation, they provide a robust safety net for distributed systems where network instability is guaranteed.

Request Handling

Pagination — Offset vs Cursor-based

What is the basic purpose of pagination in a REST API, and why is it considered a best practice?

The primary purpose of pagination is to break down large datasets into smaller, manageable chunks, which we call pages. It is a best practice because sending a massive response containing thousands of records can lead to high latency, increased memory consumption on the server, and potential timeouts for the client. By limiting the number of items per response, we ensure consistent performance and keep the payload size predictable for developers consuming the API.

How does offset-based pagination work, and what are its key implementation steps?

Offset-based pagination is implemented using two primary query parameters: 'limit' and 'offset'. The 'limit' parameter defines the maximum number of records to return, while the 'offset' defines how many items to skip from the beginning of the dataset. For instance, to get the second page with a limit of 10, the client requests 'offset=10'. The server then executes a database query using 'LIMIT 10 OFFSET 10'. It is very straightforward to implement, but it relies on the absolute position of records, which can be problematic if the underlying data changes frequently.

What is cursor-based pagination, and how does it differ from offset-based pagination in terms of functionality?

Cursor-based pagination replaces the 'offset' parameter with a 'cursor' or 'pagination token'. Instead of saying 'skip 20 items', the client provides a unique pointer—usually an encoded string representing the ID or timestamp of the last item seen in the previous page. The server queries the database for records where the ID is greater than the cursor value. This approach is superior for real-time data because it is not affected by records being inserted or deleted before the current page, ensuring that the client never sees duplicate items or skips records during a continuous fetch.

Could you compare offset-based and cursor-based pagination regarding performance and user experience?

When comparing the two, offset-based pagination performs poorly on large datasets because the database must scan and discard all skipped rows before reaching the desired page, which is an O(N) operation. Cursor-based pagination is much faster because it uses a direct index lookup for the cursor, resulting in O(log N) or O(1) performance regardless of how deep the user is in the data. While offset allows for random page navigation—like jumping to page 50—cursor-based pagination is strictly sequential, which forces a 'load more' user interface pattern but provides a much more stable and efficient experience for the end user.

Why does offset-based pagination often lead to data inconsistency issues in dynamic environments?

Data inconsistency occurs in offset-based pagination because the absolute position of records is unstable. If a user is on page 1 and a new record is inserted at the top of the dataset before they request page 2, all subsequent records shift down by one position. Consequently, the last record from page 1 will reappear as the first record on page 2. This duplicate data delivery is confusing for users and can break business logic, whereas cursor-based pagination remains pinned to specific record identifiers, ensuring that the sequence remains logically consistent regardless of concurrent insertions or deletions.

In a high-scale system, how would you design the response structure for a cursor-based pagination API?

For a robust cursor-based design, the response should include the data array and a 'meta' or 'links' object to assist the client. The 'links' object should provide a 'next' URL that contains the encoded cursor for the subsequent page. For example, if the data is ordered by creation date, the 'next' URL would look like '/api/v1/resources?limit=25&cursor=eyJpZCI6IDEwMn0='. Including a 'has_more' boolean flag in the response is also highly recommended so the frontend knows when to hide the 'load more' button. This opaque token approach allows the server to change the underlying sorting logic or database schema without breaking the client-side implementation, as the client only cares about the token provided by the server.

Filtering, Sorting, and Searching

How should a REST API implement basic filtering for a collection resource?

Basic filtering in a REST API is best implemented using query parameters. For example, if you are fetching a list of users, you should use `GET /users?status=active`. This approach is ideal because it keeps the URL clean and adheres to standard HTTP conventions. By using key-value pairs, the server can easily parse the request and apply the corresponding database filters without exposing complex internal query structures to the client.

What is the standard way to handle sorting in a RESTful API request?

The standard approach for sorting is to use a dedicated query parameter, commonly named 'sort'. To allow for flexible ordering, you might use a format like `GET /products?sort=price,desc`. This design is effective because it allows the client to explicitly state both the field name and the direction of the sort. Implementing it this way ensures that the API remains predictable and intuitive for developers consuming the resource collection.

How can you implement pagination to avoid returning excessively large datasets?

Pagination is crucial for performance and scalability. You should use parameters like 'limit' and 'offset' or 'page' and 'size', such as `GET /orders?limit=20&offset=40`. This prevents the server from overloading memory or bandwidth when a collection contains thousands of items. By forcing clients to request data in chunks, you ensure that the API remains responsive, and providing metadata in the response header helps clients navigate through the collection efficiently.

How should a developer design a search endpoint that involves complex filtering and keyword matching?

For complex searching, relying solely on query parameters can lead to 'URL soup' that exceeds character limits. Instead, design a search endpoint using `POST /items/search`. This allows you to send a structured JSON body containing multiple filters, ranges, and text keywords. Using a POST request for search is acceptable in REST because the action is idempotent—it retrieves data without modifying the server state—and it provides a clean, extensible interface for complicated queries.

Compare the use of separate query parameters versus a nested filtering syntax for complex REST API design.

Separate query parameters, like `?status=active&type=user`, are easy to implement for simple, flat data. However, they struggle with deep filtering or boolean logic. A nested syntax, such as `?filter[status]=active&filter[date][gt]=2023-01-01`, provides a structured way to handle complex conditions. While nested syntax is harder to parse server-side, it is much more scalable for enterprise applications where clients frequently need to apply multifaceted logic to collections, avoiding the confusion of repeating parameter keys.

How do you handle 'partial response' or 'field selection' in a REST API to optimize network traffic?

Field selection, often implemented via a 'fields' parameter like `GET /users?fields=id,email,username`, allows the client to request only the specific attributes they need. This is highly effective for optimizing network traffic, especially on mobile devices with limited bandwidth. By reducing the size of the payload, you decrease serialization time on the server and parsing time on the client, leading to a much more efficient interaction overall.

Request Validation and Error Responses

Why is it important to perform request validation in a REST API?

Request validation is the primary line of defense in a REST API. By verifying the structure, data types, and constraints of an incoming payload before it reaches your business logic or database, you prevent malformed data from causing internal crashes or security vulnerabilities like injection attacks. Furthermore, early validation allows you to provide immediate, constructive feedback to the client, which improves the overall developer experience and reduces unnecessary server-side processing for requests that would have failed eventually.

Which HTTP status code should be returned when a client submits invalid data, and why?

When a client submits data that fails validation, you should return an HTTP 400 Bad Request status code. This status code is specifically designed for situations where the server cannot or will not process the request due to something that is perceived to be a client error, such as a malformed request syntax or invalid request message framing. Using 400 is essential for RESTful semantics because it clearly communicates that the fault lies with the request sent, not with the server state or server-side logic.

How should an API error response be structured to be most useful for a consumer?

An effective error response should return a JSON object that provides both machine-readable and human-readable information. At a minimum, it should include a standardized error code, a descriptive message, and an optional array of specific field errors. For example: {"code": "INVALID_INPUT", "message": "The request failed validation", "details": [{"field": "email", "message": "Must be a valid email format"}]}. This structure allows client developers to programmatically handle errors and provides the end user with enough context to correct their mistakes without needing internal system logs.

Compare returning a generic error message versus returning field-specific validation errors. When should you use each?

Generic errors, such as 'Invalid Request Body', are appropriate when you want to minimize information leakage for security reasons, perhaps to prevent enumeration attacks. However, in most REST API designs, field-specific errors are far superior for developer experience. Providing exact details about which field failed and why allows the client to fix the issue immediately. You should use field-specific responses for standard public APIs to maximize usability, while reserving generic error messages for sensitive authentication endpoints where security is the absolute highest priority.

How do you handle validation for complex, nested JSON objects in a REST API?

For complex nested objects, it is best to use a schema-based validation approach, such as JSON Schema or a dedicated data validation library. Instead of writing deeply nested 'if-else' statements, you define a schema that enforces structure, mandatory fields, and types at all levels. By decoupling the validation rules from your controller logic, you keep the code maintainable. When a nested validation fails, the validator should collect all errors into a hierarchical path, such as 'user.address.zipCode', so the client knows exactly where the violation occurred in the object tree.

What is the role of the 422 Unprocessable Entity status code, and how does it differ from 400 Bad Request?

While both indicate client errors, 400 Bad Request is technically meant for syntax errors, such as a malformed JSON string or invalid character encoding. In contrast, the 422 Unprocessable Entity status code—defined in WebDAV extensions—is the standard way to indicate that the request is well-formed syntactically, but contains semantic errors. For example, if a user submits a valid JSON object where an 'age' field is set to 'negative five', the request is structurally correct but semantically invalid, making 422 the most precise RESTful choice for business logic validation.

Content Negotiation

What is the primary purpose of content negotiation in a REST API design?

Content negotiation is a mechanism defined in the HTTP specification that allows a client and a server to agree on the best representation of a resource to exchange. In REST API design, it is crucial because it promotes decoupling between the resource data and the format in which that data is delivered. By using headers like 'Accept' and 'Content-Type', a single endpoint can serve multiple formats—such as JSON, XML, or HTML—based on the specific requirements of the requesting client, which enhances API flexibility and interoperability.

How do the 'Accept' and 'Content-Type' headers function during a request-response cycle?

The 'Accept' header is sent by the client to tell the server which media types are acceptable for the response, such as 'application/json'. Conversely, the 'Content-Type' header is sent by the server to inform the client about the actual media type of the payload being returned. This pair of headers facilitates a contract: the client requests a format it can process, and the server confirms what it is providing, ensuring that the client does not encounter parsing errors or unexpected data structures.

What happens if a client requests a media type that the server cannot provide, and how should an API handle it?

If a server receives a request with an 'Accept' header for a format it does not support, it should not simply return the default format. Instead, it must return a 406 Not Acceptable HTTP status code. This informs the client that the server cannot satisfy the requested content characteristics. A well-designed API should also include a body in the 406 response that lists the available media types, helping the developer understand how to correctly adjust their request to achieve a successful outcome.

Could you compare using media type negotiation via headers versus using file extensions in a URL?

Using HTTP headers is the standard RESTful approach because it keeps the URL clean and focused on resource identification, whereas adding file extensions like '/api/users/1.json' conflates the resource identifier with the representation format. Headers allow for more complex negotiation, such as quality values (e.g., 'Accept: application/json;q=0.9, application/xml;q=0.8'), which let the client rank preferences. Extensions are easier to type in a browser, but they violate the REST principle that the URI should represent the resource identity, not the presentation format.

How does server-driven content negotiation support API versioning strategies?

Server-driven content negotiation allows for elegant API versioning by using custom vendor-specific media types. Instead of putting versions in the URL, such as '/v1/users', one can use 'Accept: application/vnd.myapi.v1+json'. This allows the server to change the response schema for a resource while keeping the URI stable. It is a powerful pattern because it enables a transition period where a single resource endpoint can handle multiple versions of a schema simultaneously, allowing clients to migrate at their own pace without needing immediate, breaking URI changes.

Explain the role of the 'Vary' header in content negotiation and why it is critical for caching.

The 'Vary' header is essential when a server supports content negotiation because it tells intermediate caches—like CDNs or browser caches—which request headers were used to determine the response representation. If a server returns different content for 'Accept: application/json' versus 'Accept: application/xml', it must send 'Vary: Accept'. Without this header, a cache might serve the cached XML response to a client that specifically requested JSON, leading to broken client applications. The 'Vary' header ensures that caches treat these representations as distinct entities based on the negotiation parameters.

Security

Authentication — API Keys, JWT, OAuth2

What is an API key and how is it typically used in REST API design?

An API key is a unique identifier, often a long alphanumeric string, passed by a client to an API to authenticate the calling project or user. In REST API design, it is commonly sent in the header, such as 'X-API-KEY: your_key_here', or as a query parameter. It is primarily used for tracking usage, enforcing rate limits, and identifying the consumer, though it is not a secure method for user authentication because it lacks dynamic expiration.

What is a JSON Web Token (JWT) and why is it preferred for stateless authentication?

A JWT is a compact, URL-safe token used to transmit information between parties as a JSON object. It consists of a header, payload, and signature. In REST APIs, it is preferred for statelessness because the server does not need to store session data in a database. Instead, the server validates the signature using a secret or public key to trust the claims contained within the token, allowing the server to handle requests independently and scale horizontally with ease.

How does the OAuth2 Authorization Code flow work and when should it be implemented?

The OAuth2 Authorization Code flow is a protocol for delegating access without sharing credentials. The client redirects the user to an authorization server, which returns an authorization code upon successful login. The client then exchanges this code for an access token via a secure server-to-server request. This flow is critical for REST APIs that need to access protected resources on behalf of a user while ensuring the client never sees the user’s primary login credentials.

Compare the security implications of using API Keys versus JWTs for API authentication.

API keys are generally static and intended for identification rather than secure authentication; if intercepted, they provide permanent access until revoked. Conversely, JWTs offer short-lived, cryptographically signed claims that are better suited for user identity. While JWTs are more secure due to expiration, they are harder to revoke if compromised compared to API keys. Thus, API keys suit server-to-server internal services, while JWTs are ideal for user-centric sessions in public-facing REST architectures.

Explain the significance of token scopes in OAuth2 and how they enforce security in REST API design.

Token scopes are strings defined by the API developer to limit what an access token can actually do. By requiring a specific scope like 'read:profile' or 'write:orders', the API enforces the principle of least privilege. In the controller logic, the system checks the token's scope claims: 'if (token.scopes.contains('write:orders')) { processOrder(); }'. This ensures that even if a token is stolen, the attacker is limited to the subset of permissions granted by the authorized scope.

How should a REST API handle token revocation if JWTs are inherently stateless and cannot be 'deleted' from the server?

Because JWTs contain all necessary info, they cannot be natively revoked before expiration. To solve this in REST API design, developers often implement a 'blacklist' or 'revocation list' using a high-performance cache like Redis. When a user logs out, the token ID (jti) is stored in the cache until its original expiry. Every incoming request must check the cache: 'if (cache.contains(request.token.jti)) { return 401; }'. This maintains stateless performance while adding the necessary capability to invalidate tokens immediately.

Authorization — RBAC and Scopes

What is the fundamental difference between RBAC and Scopes in the context of REST API design?

RBAC, or Role-Based Access Control, organizes permissions around user identities. You assign a user to a 'role' like 'Admin' or 'Editor,' and the API checks if that role is permitted to perform a specific action. Scopes, conversely, are typically used in delegation patterns like OAuth. Instead of focusing on who the user is, scopes describe what the API client is allowed to do on behalf of the user, such as 'read:profile' or 'write:orders'. RBAC is generally better for internal administrative access, while scopes provide granular, temporary authority for third-party applications.

Why is it considered a best practice to use scopes rather than roles when implementing third-party API integration?

Using roles for third-party integrations is a security anti-pattern because it grants excessive implicit trust. If you assign a 'Manager' role to a third-party application, it gains all permissions associated with that role, which may include sensitive actions the user didn't intend to delegate. Scopes allow for the Principle of Least Privilege. By requesting specific scopes like 'read:reports', you ensure the application can only perform the exact task required, significantly limiting the blast radius if the client application is compromised or acts maliciously.

How would you design an API endpoint to enforce authorization when a request requires multiple scopes?

When an endpoint requires multiple scopes, your API gateway or middleware must perform an intersection check. For example, if an endpoint for updating a financial record requires both 'finance:read' and 'finance:write', the logic should verify that the decoded token's scope claim contains all required items. You might implement this by iterating through the token scopes and ensuring the set of required scopes is a subset of the provided scopes: 'if (!tokenScopes.containsAll(requiredScopes)) throw new ForbiddenException();'. This ensures the client has explicitly been granted every piece of authority necessary to complete the operation.

Compare the 'Global Scope' approach versus 'Resource-Specific Scope' approach for complex REST APIs. When would you choose one over the other?

Global scopes are broad permissions like 'admin' or 'user' that apply to the entire API surface. They are easy to implement but lack precision. Resource-specific scopes, like 'photos:upload' or 'photos:delete', define fine-grained access to specific resources. You should choose resource-specific scopes when building a multi-tenant or complex system where different clients need to interact with distinct parts of your data without total exposure. Global scopes are sufficient for simple, monolithic internal applications, but resource-specific scopes are mandatory for scalable, secure enterprise APIs that handle diverse data types and varied client needs.

How do you handle 'Authorization Context' when a user has a role, but the specific resource belongs to a different owner?

Even if a user has a high-level role like 'Editor', you must perform an additional 'Relationship Check' or 'Attribute-Based' validation. REST APIs should not rely solely on the role claim. Before executing a `PATCH` or `DELETE` request, the API must verify if the user has a relationship with the resource ID in the URL. For example, if a user tries to access `/orders/123`, the API code must query the database to confirm `order.ownerId === user.id` or that the user has an explicit delegation record. This layer of 'Resource Ownership' acts as a secondary gate, preventing IDOR (Insecure Direct Object Reference) vulnerabilities.

Describe a scenario where you would use a 'Policy-Based' authorization strategy instead of simple RBAC, and explain why it is more robust.

Simple RBAC fails when authorization depends on dynamic data like time of day, current IP, or complex resource state. Policy-Based Access Control (PBAC) allows you to define policies as code, such as 'Only allow access if user has manager role AND the request is made during business hours AND the resource status is set to draft'. This is more robust because it decouples authorization logic from your business domain code. By centralizing these policies in an authorization engine, you avoid 'if/else' spaghetti code inside your controller layer and gain the ability to audit and update access policies globally without redeploying your core API logic.

CORS — Cross-Origin Resource Sharing

What is the primary purpose of CORS in the context of REST API design?

CORS, or Cross-Origin Resource Sharing, is a browser-based security mechanism that allows a server to explicitly indicate which origins are permitted to access its resources. In REST API design, it is essential because browsers enforce the Same-Origin Policy, which restricts scripts on one domain from making requests to another. CORS allows us to safely bypass this restriction by adding specific HTTP headers like 'Access-Control-Allow-Origin', ensuring that APIs remain secure while still allowing legitimate cross-domain data consumption by client applications.

How does the CORS preflight request work, and why is it triggered?

A preflight request is an automatic 'OPTIONS' request sent by the browser before the actual data-changing request is made. It is triggered when the request is considered 'non-simple', meaning it uses methods like PUT, DELETE, or custom headers. The purpose is to ask the server for permission before sending sensitive data. The server responds with headers indicating if the requested method and headers are allowed, protecting the API from unauthorized actions by confirming the client’s intentions.

What is the difference between an 'Access-Control-Allow-Origin' header value of '*' versus a specific domain?

Setting 'Access-Control-Allow-Origin' to '*' makes the API public, allowing any domain to read the response. This is often used for open, public data sets. However, if your API requires authentication or uses credentials, you cannot use '*' as the value; you must define the specific, trusted origin. Using a specific domain is significantly more secure because it limits the blast radius and prevents malicious third-party websites from making authenticated requests on behalf of your users.

Compare the 'Access-Control-Allow-Credentials' header approach to using a proxy server to handle cross-origin requests.

Using 'Access-Control-Allow-Credentials' allows the browser to send cookies or Authorization headers alongside the cross-origin request, which is necessary for session-based APIs. Conversely, a proxy server acts as a middleman that sits on the same origin as the client, fetching data from the API and passing it back. While credentials allow direct communication, a proxy completely bypasses CORS restrictions at the browser level, which can be useful when you lack control over the API server's response headers.

Why is it considered a security risk to reflect the 'Origin' request header directly into the 'Access-Control-Allow-Origin' response header?

Simply echoing the 'Origin' header back to the client effectively disables your CORS policy entirely, making your API as vulnerable as if you had set the policy to '*'. An attacker could host a malicious site and, because your server blindly trusts any incoming origin, the browser would allow the malicious site to read your API data. A secure design requires maintaining a strict allow-list of known, trusted domains on your server to validate the 'Origin' header before reflecting it.

How would you design a robust CORS policy for a REST API that supports both public read-only endpoints and private, authenticated endpoints?

I would implement a middleware layer that inspects the request path and the incoming 'Origin'. For public endpoints, I would permit a wider range of origins or even '*'. For authenticated endpoints, I would enforce strict origin matching against a database of allowed domains, ensuring 'Access-Control-Allow-Credentials' is only returned if the origin is explicitly trusted. I would also ensure the server responds with appropriate 'Vary: Origin' headers to prevent caching issues, ensuring that the browser correctly caches CORS responses based on the specific origin requesting the resource.

Rate Limiting

What is rate limiting, and why is it essential for a REST API?

Rate limiting is a technique used to control the amount of incoming traffic to a network resource by restricting how many requests a user or client can make within a specific timeframe. It is essential for protecting the API against overload, preventing malicious behavior like DDoS attacks, and ensuring fair usage among all consumers. Without rate limiting, a single client could consume all available server resources, causing latency or service outages for every other user relying on the system.

Which HTTP status code should you return when a client exceeds their rate limit, and what additional information should be included in the response?

When a client exceeds the defined quota, the API should return the 429 Too Many Requests status code. Simply returning the code is insufficient; the response should ideally include a Retry-After header, which specifies the number of seconds the client must wait before making another request. Additionally, providing descriptive headers like X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset helps the client understand their current status and programmatically adjust their request rate to avoid further errors.

How does the Fixed Window algorithm work, and what are its primary limitations?

The Fixed Window algorithm tracks requests within a rigid timeframe, such as 1,000 requests per hour. The counter resets exactly at the start of the next hour. The primary limitation is the 'burst' issue at the edges of the window; a user could potentially send 1,000 requests at the very end of one hour and another 1,000 at the very start of the next, effectively doubling their limit in a short span and overwhelming the server.

Compare the Fixed Window algorithm to the Sliding Window Log approach.

While the Fixed Window approach is simple to implement and memory-efficient because it only tracks a single integer counter, it suffers from burst spikes at window boundaries. Conversely, the Sliding Window Log approach tracks timestamps for every individual request, allowing for a much more precise and smoother distribution of traffic. However, the trade-off is significantly higher complexity and memory consumption, as the server must store the history of every request made by each user within the rolling window timeframe.

How would you implement a Token Bucket algorithm for rate limiting in a distributed system?

In a Token Bucket system, a 'bucket' holds tokens at a fixed rate, and each incoming request must consume one token. If the bucket is empty, the request is rejected. For a distributed system, you cannot store these buckets in local memory. Instead, you should use a centralized data store like a distributed cache. Using an atomic operation, such as a Lua script in an in-memory data store, ensures that the token consumption remains consistent and thread-safe across multiple horizontally scaled application nodes.

Explain how to handle global rate limits versus per-user rate limits in a REST API design.

Global rate limits protect the entire infrastructure from total failure and are often enforced at the API Gateway level by tracking total requests per second, regardless of origin. Per-user limits are more granular, typically enforced by extracting an identifier like an API key, OAuth token, or IP address from the request header. A robust architecture uses a layered approach: global limits provide the first line of defense against infrastructure-wide spikes, while per-user limits ensure fair resource allocation and prevent individual clients from monopolizing the system capacity.

HTTPS and TLS

What is the primary purpose of using HTTPS in a REST API design?

The primary purpose of using HTTPS in a REST API is to provide a secure communication channel between the client and the server. By utilizing TLS, HTTPS encrypts the data being transmitted over the network, which protects sensitive information like authentication tokens, user credentials, and proprietary business data from being intercepted by man-in-the-middle attackers. In modern API design, HTTPS is a non-negotiable standard because it ensures data integrity, meaning the payload cannot be altered in transit without detection, and it provides server authentication so the client knows it is talking to the intended API endpoint.

How does a TLS handshake contribute to the security of an API request?

A TLS handshake is the essential sequence of events that occurs before any actual REST API data is exchanged. It begins with the client and server agreeing on encryption algorithms and exchanging digital certificates to verify the server’s identity. Once the server proves its identity via a Certificate Authority, the handshake establishes symmetric session keys. This process is crucial because it ensures that all subsequent HTTP traffic, such as GET or POST requests, is encrypted, preventing eavesdropping and ensuring that sensitive request headers or body contents remain private throughout the entire session lifecycle.

What is the difference between HSTS and standard HTTPS in the context of API security?

While standard HTTPS provides encryption and authentication for each individual request, HTTP Strict Transport Security (HSTS) is a security policy mechanism that enforces the use of HTTPS for all future connections. Without HSTS, a client might initially attempt an unencrypted HTTP connection before being redirected to HTTPS, leaving that initial request vulnerable to interception. By setting an HSTS header, such as 'Strict-Transport-Security: max-age=31536000', the API forces the client to use only HTTPS for a specified duration, effectively mitigating protocol downgrade attacks and ensuring that no plain-text traffic ever leaves the client.

Can you compare the security implications of using TLS termination at the load balancer versus end-to-end encryption for a REST API?

TLS termination at the load balancer involves decrypting incoming traffic at the edge, allowing the internal infrastructure to handle simple HTTP traffic. While this simplifies internal routing and reduces CPU load on individual microservices, it creates a vulnerability if the internal network is compromised. Conversely, end-to-end encryption maintains the TLS tunnel from the client all the way to the destination service. In high-security API design, end-to-end encryption is superior because it ensures that even internal traffic cannot be sniffed by unauthorized actors within the data center, maintaining a strict zero-trust security posture.

How does TLS facilitate client-side authentication in a REST API architecture?

Beyond protecting data, TLS can be leveraged for Mutual TLS (mTLS), where both the client and the server present digital certificates. In this design, the server requires the client to prove its identity by validating its certificate against a trusted CA. This is highly effective for machine-to-machine communication in a REST API ecosystem because it moves authentication to the transport layer. Instead of relying solely on API keys or Bearer tokens in headers, mTLS ensures that only clients with a valid, pre-authorized certificate can even establish a connection to the API, providing a robust, cryptographically secure access control layer.

How should an API designer handle TLS version deprecation and cipher suite selection?

An API designer must strictly enforce the use of modern TLS protocols—specifically TLS 1.2 or 1.3—and disable outdated versions like SSL or TLS 1.0/1.1 due to their cryptographic weaknesses. This is typically managed at the server or gateway configuration level. Furthermore, one should explicitly define a list of secure cipher suites that support Perfect Forward Secrecy (PFS), such as 'ECDHE-RSA-AES256-GCM-SHA384'. By limiting the server to these strong, modern suites, you ensure that if a long-term private key is ever compromised, past sessions remain secure. Regular auditing of these configurations is vital to protect the API against evolving threats.

Documentation

OpenAPI 3.0 and Swagger

What is the fundamental purpose of OpenAPI 3.0 in the context of REST API design?

OpenAPI 3.0 acts as a standardized machine-readable specification format for RESTful APIs. Its primary purpose is to decouple the API definition from the implementation, serving as a 'contract' between the client and server. By defining endpoints, request structures, and response schemas, it enables automated documentation, client SDK generation, and server stub generation, which ensures that both parties adhere to the same technical interface, reducing integration friction.

How does the 'components' section in an OpenAPI 3.0 document improve the quality of an API design?

The components section is crucial for maintaining a dry (Don't Repeat Yourself) architecture in API design. Instead of redefining identical data objects or authentication schemes across multiple paths, you define them once under 'components/schemas' or 'components/securitySchemes'. This promotes consistency and ease of maintenance; when an underlying data structure needs to change, you update it in one central location rather than hunting through hundreds of paths, ensuring your API remains modular.

Can you explain the significance of the 'servers' array in an OpenAPI 3.0 specification?

The 'servers' array defines the base URL context for your API operations. In modern REST API design, this is essential because it allows developers to configure multiple environments, such as development, staging, and production, within a single file. By providing variables for URLs, tools can dynamically swap between these environments. It effectively communicates the connectivity requirements and the various endpoints a consumer must target to interact with the service successfully.

Compare the approach of 'Design-First' versus 'Code-First' when using OpenAPI 3.0.

In the 'Design-First' approach, you write the OpenAPI specification before writing any code, treating the YAML file as the source of truth; this encourages better API planning and collaboration with stakeholders. In 'Code-First', you write the code and use annotations or reflection to generate the OpenAPI document automatically. Design-First is superior for complex systems as it ensures the contract is deliberate, while Code-First is faster for rapid prototyping but can lead to bloated, implementation-leaking documentation.

How does OpenAPI 3.0 handle content negotiation using the 'mediaType' field?

OpenAPI 3.0 provides robust support for content negotiation by allowing you to define different schemas for the same request or response based on the 'Content-Type' header. For example, you can specify an application/json schema and an application/xml schema for the same endpoint path. This enables the API to be flexible and explicit about what formats it consumes and produces, ensuring that clients clearly understand the data representation expectations through the contract documentation.

How would you design a self-documenting link relationship system within an OpenAPI 3.0 specification using the 'links' object?

The 'links' object in OpenAPI 3.0 allows you to define HATEOAS-style relationships between operations. By associating a link with a response, you can instruct the client on what actions are possible after a successful request. For instance, after a 'POST /users' call, you can define a link pointing to 'GET /users/{id}'. This guides the client through the API lifecycle, creating a more discoverable and navigable design that relies less on out-of-band documentation and more on the API's own self-describing state transitions.

API Documentation Best Practices

What is the fundamental purpose of maintaining comprehensive API documentation, and why does it matter for a RESTful service?

The fundamental purpose of API documentation is to act as the single source of truth for developers, enabling them to integrate with your service without needing to read your internal source code. It reduces support overhead and empowers self-service discovery. In REST API design, clear documentation describing resources, methods, and status codes ensures that the client-server contract is honored consistently, leading to better developer experience and higher adoption rates.

Why is it important to define consistent naming conventions for your API endpoints and resources within your documentation?

Consistent naming conventions, such as using nouns rather than verbs for resource paths like '/users' instead of '/getUser', are critical because they make an API intuitive and predictable. When your documentation follows standard REST principles—using plural nouns and hierarchical paths—developers can guess how to interact with new resources without constant reference. This reduces friction and allows the architecture to scale logically as the API grows over time.

Compare the approach of using manually written static documents versus utilizing automated documentation tools like OpenAPI specifications.

Manually written documents often suffer from 'documentation drift,' where the text becomes out-of-sync with the actual API implementation as code changes. In contrast, using an OpenAPI specification allows you to generate documentation directly from your code or schema definitions. This ensures that the documentation is always accurate, as it is tied to the contract itself. Automated tools also enable interactive sandboxes where users can test calls directly, which static documents simply cannot provide.

How should an API designer document error responses to ensure that a client developer can effectively handle failures?

Error documentation should go beyond just listing status codes; it must explain the 'why' and 'how' of a failure. You should document a standard error object structure, such as including a 'code,' 'message,' and 'request_id.' For example, a 400 Bad Request should include a specific field-level validation message. By clearly defining these error schemas, you help developers debug their own requests without needing to reach out to your support team for clarification.

Explain the importance of documenting request and response schemas using machine-readable formats like JSON Schema.

Documenting schemas with JSON Schema is essential because it allows for automated validation and client-side code generation. By defining exactly what fields are required, their data types, and any constraints like minimum lengths or regex patterns, you provide a strict contract. This enables client developers to generate models or SDKs automatically, which significantly reduces the time to production and prevents runtime issues caused by malformed request bodies or unexpected response payloads.

When designing an API, why is it critical to include versioning strategy in your documentation and how should it be represented?

Versioning is critical because it manages the lifecycle of your API without breaking existing client integrations. You should document how you handle versions, whether via URI path (e.g., '/v1/users') or header-based versioning. Including this information prevents future confusion and allows you to communicate deprecation timelines clearly. A well-documented versioning strategy allows for graceful evolution of the API, ensuring that backward compatibility is maintained until a sunset date, which is vital for professional service reliability.

Testing

Testing APIs with Postman

What is the primary purpose of using Postman in the context of REST API design and testing?

Postman serves as a powerful development environment that allows developers to design, document, and test RESTful services efficiently. Its primary purpose is to provide a graphical interface for constructing HTTP requests, including methods like GET, POST, PUT, and DELETE, without writing manual code. It helps in validating the structure of resources, verifying status codes, and ensuring that the API contract remains consistent across different development stages, which is essential for maintaining high-quality service architecture.

How do you handle authentication within Postman when testing a secured REST API?

Handling authentication in Postman is crucial for testing endpoints that require restricted access. You should navigate to the 'Authorization' tab within the request builder and select the appropriate protocol, such as 'Bearer Token' for OAuth 2.0 or 'API Key'. For instance, if using a Bearer token, you would paste your JWT into the token field. This ensures that the Authorization header is automatically injected into every request, allowing you to test resource accessibility without manually typing headers.

Explain the role of environment variables in Postman and why they are vital for API design workflows.

Environment variables in Postman allow you to store and reuse data across different API requests, which is vital for maintaining a DRY (Don't Repeat Yourself) design workflow. Instead of hardcoding base URLs or authentication tokens, you define them as variables like {{base_url}}. This allows you to switch seamlessly between development, staging, and production environments. By centralizing these configurations, you significantly reduce human error and ensure that your testing environment remains flexible and scalable as your API grows in complexity.

Compare using Pre-request Scripts versus Tests in Postman. When would you use each?

Pre-request scripts and Tests serve distinct purposes in the lifecycle of an API request. You use Pre-request scripts to execute JavaScript code before the request is sent, such as generating dynamic timestamps or signing requests with cryptographic signatures. Conversely, Tests execute after the response is received, allowing you to validate data integrity using the pm.test() function. For example, 'pm.test("Status code is 200", function () { pm.response.to.have.status(200); });' ensures the API adheres to your design contract after every single execution.

How can you automate the testing of a REST API workflow using the Collection Runner?

Automating API testing is best achieved through the Collection Runner, which executes all saved requests within a collection in a specified sequence. You can set the iteration count, delay between calls, and inject external data files like JSON or CSV to perform data-driven testing. This is essential for verifying multi-step resource creation, such as creating a user, retrieving that user, and then deleting the user, ensuring the entire state-machine flow of your RESTful design functions correctly under various inputs.

Describe how to perform contract testing in Postman to ensure an API response matches its design specification.

Contract testing ensures that the API's actual response structure matches the defined schema, usually an OpenAPI specification. In Postman, you utilize the 'Ajv' JSON schema validator library within the 'Tests' tab. By defining a schema object and calling 'pm.expect(response).to.have.jsonSchema(schema)', you verify that field types, required properties, and data constraints are met. This approach is highly effective because it catches breaking changes in the API design immediately, forcing developers to maintain backward compatibility and adhere to the strict requirements established during the initial design phase.

Contract Testing

What is the fundamental purpose of contract testing in a REST API ecosystem?

The fundamental purpose of contract testing is to ensure that the provider and the consumer of a REST API remain in sync regarding their expectations. In a distributed architecture, if a provider changes its JSON schema or data types without notice, the consumer will break. Contract testing validates that the API requests and responses match a pre-defined agreement, catching breaking changes early in the development cycle rather than at runtime or through expensive end-to-end integration tests.

How does contract testing differ from traditional end-to-end testing in an API design context?

Traditional end-to-end testing requires the entire system to be deployed, including databases and external dependencies, which makes tests slow, flaky, and difficult to isolate. In contrast, contract testing focuses only on the interaction point between the client and the server. By testing the 'contract'—the schema, headers, and status codes—in isolation, we gain confidence that the interface is correct without needing the overhead of a full production-like environment, leading to faster build pipelines.

Can you explain the difference between consumer-driven contracts and provider-driven contracts?

Consumer-driven contracts place the burden of defining the requirements on the consumer; the consumer specifies exactly what fields they need from the provider's endpoint. This prevents the provider from breaking the specific data subset the client relies on. Provider-driven contracts, conversely, involve the API owner publishing a specification that defines everything the service can do. Consumer-driven is usually more efficient because it ensures the provider only builds what is actually necessary for the client.

How should a development team handle versioning in contract testing when introducing breaking changes?

When a breaking change is required, such as renaming a field or changing a resource structure, you should never modify the existing contract directly. Instead, implement a new version of the endpoint, such as moving from /v1/users to /v2/users. Contract tests should be updated to reflect both versions during a transition period. By maintaining parallel contracts, you allow consumers time to migrate their implementations while ensuring that the current production contract remains verified and functional.

Compare the approach of using schema-based validation versus interaction-based contract testing.

Schema-based validation checks if the response body matches a JSON structure, such as ensuring an 'id' is an integer. While useful, it is often insufficient because it doesn't verify the logic of the interaction. Interaction-based testing, however, captures the full HTTP exchange, including request headers, body content, and specific HTTP status codes. While schema validation is simpler to implement, interaction-based testing provides superior protection against the subtle behavioral regressions that frequently occur in REST API integrations.

How do you integrate contract testing into a CI/CD pipeline to ensure that a provider's code change doesn't break a consumer?

To integrate effectively, store the contract file—often a JSON or YAML document—in a shared repository or a centralized broker. In the CI pipeline, when the provider pushes code, the build process must automatically pull the latest consumer contracts. The provider's test suite should replay these contracts against the new build. If any assertion fails, such as a missing field in the JSON response, the build must fail immediately, preventing the breaking code from being deployed to any environment.

Load Testing APIs

What is the primary objective of load testing a REST API?

The primary objective of load testing a REST API is to determine how the system behaves under both expected and peak traffic conditions. By simulating multiple concurrent users, we aim to measure key performance indicators such as response time, throughput, and error rates. This testing ensures that our infrastructure can handle real-world demand without degrading the user experience or causing downtime, allowing us to identify bottlenecks in database queries, network latency, or server-side resource utilization before they impact production.

Why is it important to test API endpoints with realistic data payloads during a load test?

Testing with realistic data payloads is crucial because API performance often varies based on the size and complexity of the request body. If you test only with tiny JSON objects, you might miss performance issues related to serialization, deserialization, or database insertion times for larger payloads. A realistic payload, such as a nested resource object, forces the server to process complex logic and mapping, which provides a more accurate representation of how the application handles actual usage patterns in a production environment.

Compare 'Scalability Testing' with 'Stress Testing' in the context of REST API design.

Scalability testing measures the API's ability to maintain performance as you increase the number of system resources or user demand, often validating if horizontal scaling works as expected. In contrast, stress testing pushes the API far beyond its design limits to find the 'breaking point.' While scalability testing focuses on efficiency and growth, stress testing focuses on stability and graceful failure, ensuring the API returns standard error codes like 503 Service Unavailable rather than crashing unexpectedly under extreme, unsustainable pressure.

How does the implementation of rate limiting affect the strategy for load testing an API?

Rate limiting introduces a synthetic barrier that forces the API to reject traffic once specific thresholds are met. During load testing, you must account for this; if you do not configure your test suite to handle 429 Too Many Requests responses, your test results will be skewed. A proper strategy involves testing both scenarios: one where traffic remains below the rate limit to measure latency, and one where traffic exceeds the limit to verify that the API correctly throttles requests without impacting global system performance.

Why should you monitor database connection pools during an API load test?

Monitoring database connection pools is critical because REST APIs often rely on a pool of connections to communicate with the database. Under heavy load, if the API attempts to open more connections than the pool allows, the application will experience significant latency or connection timeouts. For example, if your connection pool is set to 20 but your concurrent API requests exceed this, the requests will queue up and wait, causing a cascade effect. Observing this during a test allows you to optimize pool settings to match expected concurrent throughput.

How would you design a load test for an API endpoint that involves long-running asynchronous background processing?

Designing a test for asynchronous operations requires a two-pronged approach: measuring the initial response time for the accepted request, and polling the status endpoint until completion. You should use a webhook or polling mechanism within your load test script to track the status resource, such as 'GET /jobs/{id}'. This ensures you capture the total end-to-end latency rather than just the time it took for the server to acknowledge the initial request. This reveals if the background worker queue is becoming a bottleneck under high concurrent request volume.

Interview Prep

REST API Interview Questions

What are the core principles that define a RESTful API?

A RESTful API is defined by the architectural constraints of Representational State Transfer. The core principles include client-server separation, which ensures a clear boundary between UI and data storage, and statelessness, meaning every request from a client must contain all information necessary to process it. Additionally, it requires a uniform interface, often using HTTP methods like GET, POST, PUT, and DELETE to perform operations on resources identified by URIs. By adhering to these constraints, we ensure that APIs are scalable, modular, and maintainable across different distributed systems.

How do you choose between using PUT and PATCH for updating a resource?

The choice between PUT and PATCH depends on the intended semantics of the update operation. According to REST principles, PUT is defined as an idempotent operation used to replace the entire resource with the provided representation; if the client sends only partial data, the missing fields are essentially nulled or reset. Conversely, PATCH is used for partial updates, where the client sends only the specific fields that need to be modified. Choosing PATCH is safer for high-concurrency environments because it reduces bandwidth consumption and prevents accidental overwriting of resource fields that the client did not intend to touch.

What is the role of HTTP status codes in a RESTful architecture, and how should they be used?

HTTP status codes are the standardized mechanism for communicating the outcome of a request to the client. They should be used semantically: 200 OK for successful retrieval, 201 Created for successful resource creation, 400 Bad Request for client-side input errors, 401 Unauthorized for missing credentials, 403 Forbidden for insufficient permissions, and 404 Not Found when a resource does not exist. Using these codes correctly allows the client-side logic to react appropriately without needing to parse the response body to determine if the operation actually succeeded or failed.

Explain the importance of HATEOAS in REST API design.

HATEOAS, or Hypermedia as the Engine of Application State, is a constraint that allows a client to dynamically interact with an API through hypermedia links provided by the server. Instead of hardcoding URIs, the client receives links in the response, such as 'links': [{'rel': 'next', 'href': '/orders?page=2'}]. This decouples the client from the server’s internal URI structure, allowing the server to change its path mapping without breaking client functionality. It transforms the API into a truly discoverable and self-documenting system, which is essential for long-term API evolution.

Compare the use of URI-based filtering versus Query Parameters for searching resources.

URI-based filtering, such as '/users/active', is useful for permanent, named logical collections, whereas query parameters like '/users?status=active' are superior for dynamic filtering and complex searching. Query parameters are more scalable because they allow for combinations of filters, pagination, and sorting without creating a combinatorial explosion of URI endpoints. For example, '/users?status=active&sort=desc&limit=10' is significantly more flexible and maintainable than creating a unique path for every possible filter combination. Generally, query parameters should be the default choice for resource filtering.

How do you handle versioning in a REST API to ensure backward compatibility?

Versioning is critical because it allows the API to evolve without breaking existing clients. There are three common approaches: URI versioning ('/v1/resource'), Query Parameter versioning ('/resource?version=1'), and Custom Header versioning. URI versioning is the most explicit and popular, as it makes caching easier and improves readability. However, header versioning allows the resource URI to remain stable across iterations. Regardless of the choice, the primary goal is to ensure that when breaking changes are introduced, the older version remains available for a transition period, allowing clients to migrate at their own pace.

REST vs GraphQL vs gRPC

What is the core philosophy behind REST, and how does it leverage HTTP?

REST, or Representational State Transfer, is an architectural style that treats everything as a resource identified by a unique URI. The philosophy relies heavily on leveraging standard HTTP methods—GET, POST, PUT, DELETE—to perform actions. Because REST maps perfectly to HTTP verbs, it provides a stateless, uniform interface. This is crucial because it allows intermediate components like proxies and caches to understand the traffic, improving scalability and enabling clients to interact with resources without knowing the internal server implementation details.

Compare the data fetching mechanisms of REST and GraphQL. When would you prefer one over the other?

REST typically uses fixed endpoints like '/users/1' which return a predetermined structure, often leading to over-fetching or under-fetching of data. GraphQL, however, allows clients to request exactly what they need in a single round trip via a query language. You would prefer REST for simpler APIs where caching at the HTTP layer is critical, whereas GraphQL is superior in complex systems where you want to minimize network overhead and allow clients to aggregate data from multiple entities in one request.

Why would an engineer choose gRPC over REST for service-to-service communication?

Engineers choose gRPC for internal microservices because it uses Protocol Buffers instead of JSON, resulting in much smaller payloads and faster serialization. While REST uses human-readable text over HTTP/1.1, gRPC runs on HTTP/2, which supports multiplexing, meaning multiple requests can be sent over a single connection simultaneously. Furthermore, gRPC generates strongly-typed client code, which catches errors at compile time rather than runtime, significantly improving performance and reliability in high-throughput backend-to-backend environments.

How does the handling of 'state' differ between REST and GraphQL, and what are the implications for caching?

In REST, state is tied to specific resources accessible via stable URIs, which makes them highly cacheable by CDNs and browsers using standard HTTP headers like ETag or Cache-Control. GraphQL, however, typically uses a single endpoint for all operations, usually via a POST request. This breaks standard HTTP caching mechanisms because the 'query' is inside the request body, not the URI. Consequently, GraphQL requires more complex, application-level caching strategies compared to the native HTTP caching benefits of REST.

Explain the trade-offs of using strongly-typed contracts in gRPC versus the flexibility of REST.

gRPC requires a strict '.proto' file that defines both the request and response structure, forcing a 'contract-first' design. This rigidity ensures that producers and consumers are perfectly aligned, eliminating 'undefined' property issues. REST is more flexible, often using JSON-schema or openAPI for documentation, but it lacks the built-in binary enforcement of gRPC. The trade-off is that REST allows for rapid prototyping and human-readable debugging, while gRPC offers superior performance and strict schema adherence that prevents breaking changes in distributed architectures.

Discuss the evolution of API design from REST to GraphQL to gRPC in terms of network efficiency.

The evolution is driven by the need for network efficiency. REST was a breakthrough for public-facing resource management but struggled with payload bloat. GraphQL evolved to solve the 'n+1' problem and over-fetching, allowing the client to define the shape of the data, which reduces latency by collapsing multiple requests into one. Finally, gRPC optimizes the transport layer itself by using binary serialization and HTTP/2. The trajectory has moved from human-readable text-based protocols designed for the browser to binary, high-performance protocols designed for machine-to-machine efficiency.