Add a Paginated GET Endpoint to an Existing Java Service, Then Design Its Production DB
Company: Okta
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Technical Screen
In a practical coding exercise on an online coding platform, you work inside a small existing service written in Java. The service already has a POST endpoint and a GET endpoint, and you may use them as references for how the codebase handles routing, request parsing, storage access, response formatting and errors. Your task is to implement one more GET endpoint and run a few simple tests against it. The second part adds pagination to that endpoint. The interviewer then asks how the design changes when the data volume becomes very large, and how you would design the database and indexes if this were a production system.
The resource and its fields are not specified. For practice, assume the service stores records of one resource type, each with a unique ID, a creation timestamp and a few attribute fields; that the existing POST endpoint creates a record and the existing GET endpoint returns one record by ID; and that the new GET endpoint lists records, which is what makes pagination meaningful in Part 2. If your environment differs, follow its conventions rather than these assumptions.
### Constraints and Clarifications
- Write Java inside the existing codebase. Reuse its storage object, helpers and error format rather than introducing a new framework.
- Parts 1 and 2 use whatever in-process storage the existing endpoints use. Parts 3 and 4 are design discussion.
- "Simple tests" means a handful of checks you can run in the coding environment, such as creating records through the existing POST path and reading them back through the new endpoint.
### Clarifying Questions
- Which fields should each listed record include, and should the endpoint support filters, or only return the whole collection?
- In what order should records be returned (creation time, ID or insertion order), and must that order stay stable across calls?
- What should the endpoint return for an empty collection and for malformed query parameters? Should errors match the existing endpoints' status codes and body format?
- For pagination, is a particular style expected (page number and size, offset and limit, or an opaque cursor), and is there a maximum page size?
### Part 1 — Implement the GET endpoint and test it
Implement the new GET endpoint so that it follows the existing endpoints' style for routing, status codes and response format. Then write a few simple tests: at least one that creates records through the existing POST endpoint and reads them back through yours, plus the edge cases you think matter.
```hint Read before you write
Study the existing POST and GET handlers first and mirror how they parse input, reach storage and build responses, so the only new logic you write is the listing itself.
```
#### What This Part Should Cover
- Consistency with the existing handlers: status codes, response shape and error format
- A deterministic order for the returned records
- Tests that go through the POST path and then the new GET path, plus empty and invalid-input cases
### Part 2 — Add pagination
Extend the endpoint so a client can fetch the collection one page at a time. Define the request parameters, the response shape (including how the client asks for the next page and knows it has reached the end), the defaults and the validation rules.
```hint Concurrent writes
Picture a client walking through the pages while another client creates or deletes a record between two of its requests. Check whether your scheme skips or repeats a record.
```
#### What This Part Should Cover
- Request parameters, defaults, the maximum page size, and validation of each
- How the next page is requested and how the end of the collection is signaled
- An ordering with a unique tie-breaker, so that no record is skipped or returned twice
### Part 3 — The data volume becomes too large
The collection grows far beyond what fits comfortably in memory or can be scanned on each request. What breaks in your implementation, and what do you change?
```hint Find the full scans
Look for every step that touches all records on every request, such as sorting, counting or skipping, and ask what it costs at a hundred times the size.
```
#### What This Part Should Cover
- Which steps of the current implementation cost time or memory proportional to the whole collection, per request
- Moving to a persistent store, and how the pagination scheme interacts with it
- Guard rails that protect the service: page-size caps, timeouts, and whether a total count is worth computing
### Part 4 — Production database and index design
If this were a production system, how would you design the database and its indexes behind these endpoints?
```hint Start from the queries
Write down the exact queries the POST, single-record GET and list endpoints will run, including the filter and the sort, and derive each index from one of them.
```
#### What This Part Should Cover
- The table schema, primary key and ID type
- Indexes that match the list query's filter and sort order, and their cost on writes
- How the design scales past one database node, and the consistency trade-offs that introduces
### What a Strong Answer Covers
- Working, idiomatic Java produced in an unfamiliar codebase within the time limit
- Explicit API semantics: status codes, ordering, empty results and invalid input
- Tests that exercise the endpoints through the same path a client uses
- A pagination scheme that stays correct under concurrent writes
- Database and index choices justified by the actual access patterns rather than listed generically
### Follow-up Questions
- How would you add a filter parameter without invalidating cursors that clients already hold?
- Should the list response include a total count, and what does that cost at scale?
- How would you evolve the response shape once external clients depend on it?
- In production, how would you notice that the list endpoint has become slow, and what would you inspect first?
Overview: A practical Java coding exercise: add a new GET endpoint to an existing service by following its POST and GET handlers, test it, then add pagination. Follow-ups ask what changes when the data volume becomes too large and how to design the production database schema and indexes behind the endpoint.
Read the full Okta Software Engineer interview experience this question came from