How would you design the backend for a simple to-do list application?
Fast recall
Quick revision
Core answer
Can the candidate design a complete but simple backend system covering API design, database schema, and basic security without getting overwhelmed by unnecessary complexity.
Jumping straight into code without explaining the overall architecture, or forgetting about authentication so all users see everyone's tasks which breaks the entire application logic.
Mention specific design decisions like 'I'd add an index on userId in the tasks table since we're filtering by user frequently' or 'I'd use DTOs to avoid exposing internal entity structure to API clients.'
Interview-ready guide
Interview answer
I would model a task as a user-owned resource. The database has a tasks table with an id, owner id, title, description, status, and timestamps. The owner id references the user, and I would index the columns used by the main list query, usually owner id and possibly status.
The API exposes create, list, update, and delete operations. GET /tasks returns only the signed-in user's tasks and is paginated. For an update or delete, the repository looks up the row by both task id and owner id. That is safer than loading by id alone because a task owned by someone else is never returned to the operation.
Spring Security supplies a trusted principal using whichever authentication design the application chose—a session, OAuth/OIDC, or a token. The client cannot send an arbitrary owner id. Controllers handle HTTP validation and DTOs, the service owns authorization and the transaction, and the repository handles persistence. I would return a not-found or forbidden response according to the API's disclosure policy and test that one user cannot access another user's task.
How would you design the backend for a simple to-do list application
Direct Answer
Design tasks as user-owned resources in a Spring Boot API. Store each task with an id, owner id, title, status, and timestamps; expose create, list, update, and delete operations; and enforce ownership in every query. Obtain the caller identity through the application's chosen authentication mechanism, validate input, return DTOs, and use a relational database transaction for each change.
Code Example
@Entity
@Table(name = "tasks", indexes = @Index(name = "idx_task_owner", columnList = "ownerId"))
class Task {
@Id @GeneratedValue private Long id;
@Column(nullable = false) private Long ownerId;
@Column(nullable = false) private String title;
@Enumerated(EnumType.STRING) private TaskStatus status;
protected Task() {} // required by JPA
// domain methods and accessors omitted
}
interface TaskRepository extends JpaRepository<Task, Long> {
Page<Task> findAllByOwnerId(Long ownerId, Pageable page);
Optional<Task> findByIdAndOwnerId(Long id, Long ownerId);
}
@Service
class TaskService {
private final TaskRepository tasks;
@Transactional
TaskResponse rename(Long ownerId, Long taskId, String title) {
Task task = tasks.findByIdAndOwnerId(taskId, ownerId)
.orElseThrow(TaskNotFoundException::new);
task.rename(title);
return TaskResponse.from(task);
}
}
Common Mistakes & Pitfalls
- Trusting
ownerIdfrom the request — derive identity from the authenticated principal and query using that identity. - Checking ownership only in the UI — an attacker can call the API directly; authorization belongs on the server for every operation.
- Returning JPA entities — response DTOs keep persistence details and lazy relationships out of the API contract.
- Ignoring list growth — paginate
GET /tasksand add an index that supports the actual owner-and-status queries after checking the query plan.
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.