Machine Coding Problem

Task Planner (Jira-lite)

maco30maco60macoAllproductivitystate-patternobserver-patternfactory-methodcapacity-check
Commonly Asked By:AtlassianMicrosoftSlack

Requirements & System Scope

Functional Scope (In-Scope)

  • Backlog & Sprint Allocations: Support Project, Sprint, and Task hierarchical trees.
  • State Machine Transitions (State Pattern): Enforce valid task status migrations using distinct TaskState pattern entities.
  • Sprint Velocity warnings: Calculate sprint story point loads, firing warnings if capacity limits are breached.
  • Observer Updates: Enable subscribers to receive log updates upon task state transitions.
  • Polymorphic Task Types: Factory resolution for Feature and Bug tasks.

Explicit Boundaries (Out-of-Scope)

  • No Automatic Agile Release Planning AI: Bypasses sophisticated auto-balancing resources or burn-down predictions.
  • No Rich Document Comments editor: File system lacks complex collaborative text threads, file attachment uploads, or Markdown styling.

Class Diagram & Entity Relationships

Structural layout showing relationships between tasks, state patterns, and sprint lanes:

Loading...
  • State Design Pattern Representation: States are encapsulated in individual strategy objects, preserving SOLID cleanliness.
  • Observer updates: Status modifications broadcast to all registered observers dynamically.

Design Patterns & SOLID Principles

  • State Design Pattern (Task Lifecycles): Decouples complex transition logics from the task entity itself, defining discrete behaviors inside dedicated state classes (satisfies SRP).
  • Factory Method Pattern (Task Creation): Encapsulating Bug and Feature task configurations within a simple factory guarantees code expansion (OCP) as additional card types are created.
  • Observer Pattern (Task Subscriptions): Enables observers (e.g. dashboards, user push systems) to subscribe to card state notifications dynamically.

Core Execution Workflows

Task Allocation Sequence

  1. Caller requests new task creation: TaskFactory.createTask(BUG, "JIRA-1", "Fix Login", 3).
  2. Register task item to backlog lists.
  3. Assignee allocates cards to Sprint bucket: sprint.addTask(task).
  4. Sum total Sprint allocated story points:
    1. If the new total exceeds the sprint cap, trigger capacity alerts.
    2. Otherwise, append the task.
  5. During execution, assignees transition task status: task.transitionTo(IN_PROGRESS), delegating evaluation directly to the current TaskState strategy.

Concurrency & Thread Safety Strategy

When multiple team members attempt to modify, allocate, or update tasks simultaneously within sprint buckets:

  • Atomic Status Transition Locks: Acquire exclusive locks per task prior to altering state values or notifying listeners.
  • Synchronized Sprints: Guard additions to sprint lists with locks to prevent concurrent sum calculations from triggering race conditions.

Complete Clean Code Blueprint

Clean reference designs detailing sprint capacities in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

enum TaskStatus { TODO, IN_PROGRESS, REVIEW, DONE }
enum TaskType { BUG, FEATURE, STORY }

interface TaskState {
    boolean transitionTo(Task task, TaskStatus nextStatus);
    TaskStatus getStatus();
}

class TodoState implements TaskState {
    @Override
    public boolean transitionTo(Task task, TaskStatus nextStatus) {
        if (nextStatus == TaskStatus.IN_PROGRESS) {
            task.setState(new InProgressState());
            return true;
        }
        return false;
    }
    @Override
    public TaskStatus getStatus() { return TaskStatus.TODO; }
}

class InProgressState implements TaskState {
    @Override
    public boolean transitionTo(Task task, TaskStatus nextStatus) {
        if (nextStatus == TaskStatus.REVIEW) {
            task.setState(new ReviewState());
            return true;
        } else if (nextStatus == TaskStatus.TODO) {
            task.setState(new TodoState());
            return true;
        }
        return false;
    }
    @Override
    public TaskStatus getStatus() { return TaskStatus.IN_PROGRESS; }
}

class ReviewState implements TaskState {
    @Override
    public boolean transitionTo(Task task, TaskStatus nextStatus) {
        if (nextStatus == TaskStatus.DONE) {
            task.setState(new DoneState());
            return true;
        } else if (nextStatus == TaskStatus.IN_PROGRESS) {
            task.setState(new InProgressState());
            return true;
        }
        return false;
    }
    @Override
    public TaskStatus getStatus() { return TaskStatus.REVIEW; }
}

class DoneState implements TaskState {
    @Override
    public boolean transitionTo(Task task, TaskStatus nextStatus) {
        return false; // Done is terminal
    }
    @Override
    public TaskStatus getStatus() { return TaskStatus.DONE; }
}

interface TaskObserver {
    void onTaskStatusChanged(String taskId, TaskStatus oldStatus, TaskStatus newStatus);
}

class ConsoleTaskLogger implements TaskObserver {
    @Override
    public void onTaskStatusChanged(String taskId, TaskStatus oldStatus, TaskStatus newStatus) {
        System.out.println(String.format("[Observer Log] Task: %s transitioned from %s to %s", taskId, oldStatus, newStatus));
    }
}

abstract class Task {
    private final String id;
    private final String title;
    private final int storyPoints;
    private TaskState state;
    private String assignee;
    private final List<TaskObserver> observers = new CopyOnWriteArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();

    public Task(String id, String title, int storyPoints) {
        this.id = id;
        this.title = title;
        this.storyPoints = storyPoints;
        this.state = new TodoState();
    }

    public String getId() { return id; }
    public String getTitle() { return title; }
    public int getStoryPoints() { return storyPoints; }
    
    public TaskStatus getStatus() { 
        lock.lock();
        try { return state.getStatus(); } finally { lock.unlock(); }
    }
    
    public void setAssignee(String assignee) {
        lock.lock();
        try { this.assignee = assignee; } finally { lock.unlock(); }
    }
    
    public String getAssignee() {
        lock.lock();
        try { return assignee; } finally { lock.unlock(); }
    }

    public void setState(TaskState state) {
        this.state = state;
    }

    public void addObserver(TaskObserver observer) { observers.add(observer); }
    public void removeObserver(TaskObserver observer) { observers.remove(observer); }

    public boolean transitionTo(TaskStatus nextStatus) {
        lock.lock();
        try {
            TaskStatus oldStatus = state.getStatus();
            if (state.transitionTo(this, nextStatus)) {
                notifyObservers(oldStatus, nextStatus);
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    private void notifyObservers(TaskStatus oldStatus, TaskStatus newStatus) {
        for (TaskObserver obs : observers) {
            obs.onTaskStatusChanged(id, oldStatus, newStatus);
        }
    }
}

class BugTask extends Task {
    public BugTask(String id, String title, int storyPoints) { super(id, title, storyPoints); }
}

class FeatureTask extends Task {
    public FeatureTask(String id, String title, int storyPoints) { super(id, title, storyPoints); }
}

class TaskFactory {
    public static Task createTask(TaskType type, String id, String title, int storyPoints) {
        switch (type) {
            case BUG: return new BugTask(id, title, storyPoints);
            case FEATURE: return new FeatureTask(id, title, storyPoints);
            default: throw new IllegalArgumentException("Unsupported type: " + type);
        }
    }
}

class Sprint {
    private final String name;
    private final int capacity;
    private final List<Task> tasks = new CopyOnWriteArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();

    public Sprint(String name, int capacity) {
        this.name = name;
        this.capacity = capacity;
    }

    public boolean addTask(Task task) {
        lock.lock();
        try {
            int currentSP = tasks.stream().mapToInt(Task::getStoryPoints).sum();
            if (currentSP + task.getStoryPoints() > capacity) {
                System.out.println(String.format("[Warning] Adding Task %s (Points: %d) breaches capacity in Sprint: %s (Max: %d)",
                        task.getId(), task.getStoryPoints(), name, capacity));
            }
            tasks.add(task);
            return true;
        } finally {
            lock.unlock();
        }
    }

    public List<Task> getTasks() { return Collections.unmodifiableList(tasks); }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== Task Planner Simulation ===");
        Sprint sprint = new Sprint("Sprint-01", 10);

        Task t1 = TaskFactory.createTask(TaskType.FEATURE, "JIRA-101", "Implement Google OAuth", 5);
        Task t2 = TaskFactory.createTask(TaskType.BUG, "JIRA-102", "Fix session timeout crash", 3);
        Task t3 = TaskFactory.createTask(TaskType.FEATURE, "JIRA-103", "Setup telemetry sinks", 5);

        TaskObserver logger = new ConsoleTaskLogger();
        t1.addObserver(logger);
        t2.addObserver(logger);

        sprint.addTask(t1);
        sprint.addTask(t2);
        sprint.addTask(t3); // Triggers capacity check log

        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(() -> t1.transitionTo(TaskStatus.IN_PROGRESS));
        executor.submit(() -> t2.transitionTo(TaskStatus.IN_PROGRESS));

        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.SECONDS);
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...