Katkat

Design a Todo List Object Model

NotionSoftware Engineer · Onsite

Last updated

Tests object-oriented design, domain modeling, and API design skills by implementing an extensible, hierarchical Todo list model with task dependency management and filtering.

Problem Statement

Design an object-oriented domain model and service interface for a hierarchical Todo List application (similar to nested checklist blocks in Notion).

Your design should model tasks, support arbitrary nesting of subtasks, enforce status transition rules, and allow querying/filtering tasks by various attributes.

Functional Requirements

  1. Task Management:
    • Create tasks with a title, optional description, priority (LOW, MEDIUM, HIGH), optional due_date, and zero or more tags.
    • Tasks can have subtasks, supporting arbitrary levels of nesting.
    • A task can be deleted, which recursively deletes all its subtasks.
  2. Status Propagation:
    • Tasks have a status: PENDING, IN_PROGRESS, or COMPLETED.
    • When a parent task is marked COMPLETED, all its descendant subtasks must also be marked COMPLETED.
    • If a subtask is marked PENDING or IN_PROGRESS, all its ancestor tasks must transition out of COMPLETED (e.g., to IN_PROGRESS).
  3. Querying and Filtering:
    • Provide a search/query API that filters tasks by status, tags, priority, or due date range.
    • Support sorting by due date or priority.

Interface

Design the underlying entities and implement the TodoListService class:

from enum import Enum
from typing import List, Optional, Set
from datetime import datetime

class Priority(Enum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3

class Status(Enum):
    PENDING = "PENDING"
    IN_PROGRESS = "IN_PROGRESS"
    COMPLETED = "COMPLETED"

class TodoListService:
    def create_task(
        self, 
        title: str, 
        description: str = "", 
        priority: Priority = Priority.MEDIUM, 
        due_date: Optional[datetime] = None, 
        tags: Optional[Set[str]] = None, 
        parent_id: Optional[str] = None
    ) -> str:
        """Creates a new task (or subtask) and returns its generated task_id."""
        pass

    def update_status(self, task_id: str, new_status: Status) -> None:
        """Updates status and propagates changes to descendants/ancestors."""
        pass

    def delete_task(self, task_id: str) -> None:
        """Deletes a task and all of its descendants."""
        pass

    def query_tasks(
        self,
        status: Optional[Status] = None,
        tag: Optional[str] = None,
        priority: Optional[Priority] = None,
        sort_by: Optional[str] = None
    ) -> List["Task"]:
        """Filters tasks across the workspace based on provided criteria."""
        pass

Example

service = TodoListService()
parent_id = service.create_task("Launch Feature", priority=Priority.HIGH)
child_id = service.create_task("Write Unit Tests", parent_id=parent_id)

# Completing parent completes child
service.update_status(parent_id, Status.COMPLETED)
assert service.get_task(child_id).status == Status.COMPLETED

# Reopening child reopens parent
service.update_status(child_id, Status.IN_PROGRESS)
assert service.get_task(parent_id).status == Status.IN_PROGRESS

Edge Cases to Consider

  • Deleting a parent node when clients still hold references to subtask IDs.
  • Preventing circular parent-child relationships if tasks can be reparented.
  • Efficient upward and downward traversals without deep recursion stack overflows.

Hints

Hint 1

Consider using the Composite pattern to treat individual tasks and nested subtask trees uniformly.

Hint 2

Decouple state propagation into separate ancestor-updating and descendant-updating routines to prevent infinite loops.

Hint 3

Design a dedicated Filter or Specification object for querying so the search interface remains open to extension without changing method signatures.

Follow-up questions

  • How would you model recurring tasks (e.g., repeats every Monday) without pre-generating infinite future task instances?
  • In a collaborative environment like Notion, how would your domain model resolve concurrent edits where one user moves a subtask while another deletes its parent?
  • How would you optimize the search and filtering queries if the list grows to hundreds of thousands of tasks per organization?