Skip to Content
Course content

228: Building a Job Application Tracker

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Alright, let's get into this. We've spent a lot of time talking about individual classes and data structures, but now I want us to actually put them together. We're building a Job Application Tracker. The goal is simple: keep track of where we've applied, the role, and the current status (Applied, Interviewing, Offered, or Rejected).

The Naive List Approach

My first instinct is usually to just throw everything into a List. It's the path of least resistance. I'll start by defining a simple JobApplication POJO and then try to manage them in a main loop.

public class JobApplication {
    String company;
    String role;
    String status;

    public JobApplication(String company, String role, String status) {
        this.company = company;
        this.role = role;
        this.status = status;
    }
    // getters and setters omitted for brevity
}

// In our main logic:
List<JobApplication> apps = new ArrayList<>();
apps.add(new JobApplication("TechCorp", "Backend Dev", "Applied"));
apps.add(new JobApplication("SoftSystems", "Java Engineer", "Applied"));

This works great for adding. But here's where I hit a snag. Imagine we get an email from TechCorp saying they want to interview us. I need to update that specific application's status. With a List, I can't just "find" TechCorp. I have to iterate through the entire list, check every object, and hope I find the right one.

for (JobApplication app : apps) {
    if (app.getCompany().equals("TechCorp")) {
        app.setStatus("Interviewing");
        break;
    }
}

It's a bit clunky, right? If we have ten applications, it's fine. If we're aggressively applying to a hundred places, this O(n) lookup starts to feel like a waste of energy.

Switching Gears to a Map

I'm thinking: why am I searching for a company name manually? I should probably be using a Map. If I use the company name as the key, I can jump straight to the application I want to update.

Map<String, JobApplication> appMap = new HashMap<>();
appMap.put("TechCorp", new JobApplication("TechCorp", "Backend Dev", "Applied"));
appMap.put("SoftSystems", new JobApplication("SoftSystems", "Java Engineer", "Applied"));

// Updating is now a one-liner
appMap.get("TechCorp").setStatus("Interviewing");

Much better. The update is now nearly instantaneous. But wait—I just realized a flaw in this design. What if I apply to TechCorp for two different roles? Maybe a "Backend Dev" role and a "Systems Architect" role? My HashMap just nuked the first application because the keys must be unique. I've accidentally deleted my own data.

Solving the Collision Problem

Okay, lesson learned. Using the company name as a key is too risky. I need a unique identifier. In a real database, this would be a primary key (like a UUID or an auto-incrementing ID). Let's introduce an applicationId to our class.

public class JobApplication {
    private int id; 
    private String company;
    // ... other fields
}

Now, I'll map the Integer ID to the JobApplication object. This solves the duplicate company problem. But now I've run into a different issue: the user doesn't want to remember an ID number; they want to see all their "Interviewing" applications. A Map is great for finding a specific item by ID, but it's useless for filtering by status.

Designing a Dedicated Manager

I've realized that trying to do everything inside the main method with a single collection is making the code messy. I need a "Service" or "Manager" class that handles the logic of how we store and retrieve this data, so the rest of the app doesn't have to care if I'm using a Map, a List, or a database.

I'll keep the Map for the fast lookups by ID, but I'll use Java Streams to handle the filtering. It gives me the best of both worlds: speed for updates and flexibility for reporting.

public class ApplicationManager {
    private Map<Integer, JobApplication> storage = new HashMap<>();
    private int nextId = 1;

    public void addApplication(String company, String role, String status) {
        JobApplication app = new JobApplication(nextId++, company, role, status);
        storage.put(app.getId(), app);
    }

    public List<JobApplication> getApplicationsByStatus(String status) {
        return storage.values().stream()
            .filter(app -> app.getStatus().equalsIgnoreCase(status))
            .collect(Collectors.toList());
    }

    public boolean updateStatus(int id, String newStatus) {
        if (storage.containsKey(id)) {
            storage.get(id).setStatus(newStatus);
            return true;
        }
        return false;
    }
}

Now the logic is encapsulated. If I decide later that I want to save these to a file or a database, I only have to change the code inside ApplicationManager. The rest of my program just calls getApplicationsByStatus() and doesn't care how the magic happens under the hood. That's the kind of separation that keeps a project from collapsing under its own weight as it grows.




📋 Practical Task

Implement a Job Application Archive Feature

Currently, our ApplicationManager keeps everything in memory. Your task is to add a "removal" and "archiving" mechanism to the manager class.

  • Modify the ApplicationManager class to include a second collection (a List or another Map) called archivedApplications.
  • Implement a method archiveApplication(int id). This method should remove the application from the active storage map and move it into the archivedApplications collection.
  • Ensure that if an ID is provided that doesn't exist in the active storage, the method returns false or throws a custom exception rather than crashing.
  • Create a method getArchivedCount() that returns the total number of archived applications.

Test your implementation by adding three applications, archiving two of them, and verifying that the active storage now only contains one item while the archive contains two.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.