DEV Community

Cover image for LLD: Building a Polling System in Java with Repository Pattern
ZeeshanAli-0704
ZeeshanAli-0704

Posted on

LLD: Building a Polling System in Java with Repository Pattern

๐Ÿ—ณ๏ธ Building a Polling System in Java with Repository Pattern (In-Memory)


Table of Contents


๐Ÿ“Œ Introduction

Polls and voting systems are everywhere โ€” from online classrooms to event feedback apps.

In this blog, weโ€™ll design and implement a Java-based Polling System that follows the Repository Pattern with an in-memory store (no database).

This project is a great way to practice:

  • Java OOP principles
  • Layered architecture & separation of concerns
  • Defensive programming & validations

โ“ Problem Statement

We want to design a polling platform where:

  • Admins can create polls with multiple options.
  • Users can vote on polls.
  • Results can be fetched per poll.

Constraints:

  • A poll must have at least 2 unique, non-empty options.
  • Options should be case-insensitive (Yes vs yes โ†’ same).
  • Only admins can create polls.

โœ… System Requirements

  • Create Polls (Admin only)
  • View Polls (per Admin, or all)
  • Record Votes (User action)
  • View Results (tally votes)

๐Ÿ—๏ธ Design Approach

๐Ÿ”น Repository Pattern

Weโ€™ll use the Repository Pattern to separate persistence from business logic.

Repositories:

  • UserRepository โ†’ manages users
  • PollRepository โ†’ manages polls
  • VoteRepository โ†’ manages votes

Currently, they use in-memory storage (ConcurrentHashMap/List), but can later be swapped with a database.

๐Ÿ”น Entities

  1. User โ†’ id, name, type (ADMIN / USER)
  2. Poll โ†’ id, question, options, createdBy
  3. Vote โ†’ pollId, userId, selectedOption

๐Ÿ—‚๏ธ ER Diagram

If we extend to a database, the schema looks like this:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    USERS      โ”‚        โ”‚    POLLS      โ”‚        โ”‚    VOTES    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค        โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค        โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ user_id (PK)  โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค created_by (FK)โ”‚       โ”‚ vote_id (PK)โ”‚
โ”‚ name          โ”‚        โ”‚ poll_id (PK)   โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ poll_id (FK)โ”‚
โ”‚ type (ADMIN/USER)โ”‚     โ”‚ question       โ”‚       โ”‚ user_id (FK)โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚ options[]      โ”‚       โ”‚ option      โ”‚
                         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top

How Your Final Code Works

The final implementation of the polling system follows the Repository Pattern using in-memory storage. This design ensures a clean separation of concerns between controllers, repositories, and models.

Execution Flow

  1. User Creation
  • UserController calls UserRepository.addUser(user).
  • Users are stored in-memory inside a Map<Integer, User>.
  1. Poll Creation
  • PollController invokes PollRepository.addPoll(poll).
  • Polls are maintained in-memory inside a Map<Integer, Poll>.
  1. Voting
  • When PollController.vote(userId, pollId, option) is called:

    • The system retrieves the User from UserRepository.
    • Retrieves the Poll from PollRepository.
    • Creates a Vote object and adds it to the poll.
    • Prevents duplicate votes from the same user.
  1. Fetching Results
  • PollController.getResults(pollId) aggregates votes per PollOption.
  • Returns results as a map of Option โ†’ Count.

Final Code Walkthrough (In-Memory Repository)

// org/example/repository/UserRepository.java
package org.example.repository;

import org.example.models.User;
import java.util.*;

public class UserRepository {
    private final Map<Integer, User> users = new HashMap<>();

    public void addUser(User user) {
        users.put(user.getId(), user);
    }

    public User getUser(int id) {
        return users.get(id);
    }
}

// org/example/repository/PollRepository.java
package org.example.repository;

import org.example.models.Poll;
import java.util.*;

public class PollRepository {
    private final Map<Integer, Poll> polls = new HashMap<>();

    public void addPoll(Poll poll) {
        polls.put(poll.getId(), poll);
    }

    public Poll getPoll(int id) {
        return polls.get(id);
    }
}

// org/example/controllers/UserController.java
package org.example.controllers;

import org.example.models.User;
import org.example.enums.UserType;
import org.example.repository.UserRepository;

public class UserController {
    private final UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(int id, String name, UserType type) {
        User user = new User(id, name, type);
        userRepository.addUser(user);
    }
}

// org/example/controllers/PollController.java
package org.example.controllers;

import org.example.enums.PollOption;
import org.example.models.*;
import org.example.repository.*;

import java.util.*;

public class PollController {
    private final PollRepository pollRepository;
    private final UserRepository userRepository;

    public PollController(PollRepository pollRepository, UserRepository userRepository) {
        this.pollRepository = pollRepository;
        this.userRepository = userRepository;
    }

    public void createPoll(int id, String question, List<PollOption> options) {
        Poll poll = new Poll(id, question, options);
        pollRepository.addPoll(poll);
    }

    public void vote(int userId, int pollId, PollOption option) {
        User user = userRepository.getUser(userId);
        Poll poll = pollRepository.getPoll(pollId);

        if (user != null && poll != null) {
            Vote vote = new Vote(user, option);
            poll.addVote(vote);
        }
    }

    public Map<PollOption, Long> getResults(int pollId) {
        Poll poll = pollRepository.getPoll(pollId);
        return poll.getVotes().stream()
                   .collect(Collectors.groupingBy(Vote::getOption, Collectors.counting()));
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


๐Ÿ’ป Implementation

Weโ€™ll keep a layered structure:

Main โ†’ Controller โ†’ Service โ†’ Repository โ†’ Models
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ Enums

UserType.java

package org.example.polling.enums;

public enum UserType {
    ADMIN,
    USER
}
Enter fullscreen mode Exit fullscreen mode

PollOption.java

package org.example.polling.enums;

public enum PollOption {
    OPTION_A,
    OPTION_B,
    OPTION_C,
    OPTION_D
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ Models

User.java

package org.example.polling.entities;

import org.example.polling.enums.UserType;

public class User {
    private final int userId;
    private final String name;
    private final UserType userType;

    public User(int userId, String name, UserType userType) {
        this.userId = userId;
        this.name = name;
        this.userType = userType;
    }

    public int getUserId() { return userId; }
    public String getName() { return name; }
    public UserType getUserType() { return userType; }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", name='" + name + '\'' +
                ", userType=" + userType +
                '}';
    }
}
Enter fullscreen mode Exit fullscreen mode

Poll.java

package org.example.polling.entities;

import org.example.polling.enums.PollOption;

import java.time.LocalDateTime;
import java.util.Map;

public class Poll {
    private final int pollId;
    private final String question;
    private final Map<PollOption, String> options;
    private final User createdBy;
    private final LocalDateTime createdAt;

    public Poll(int pollId, String question, Map<PollOption, String> options, User createdBy) {
        this.pollId = pollId;
        this.question = question;
        this.options = options;
        this.createdBy = createdBy;
        this.createdAt = LocalDateTime.now();
    }

    public int getPollId() { return pollId; }
    public String getQuestion() { return question; }
    public Map<PollOption, String> getOptions() { return options; }
    public User getCreatedBy() { return createdBy; }
    public LocalDateTime getCreatedAt() { return createdAt; }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


Vote.java

package org.example.polling.entities;

import org.example.polling.enums.PollOption;

public class Vote {
    private final int pollId;
    private final int userId;
    private final PollOption selectedOption;

    public Vote(int pollId, int userId, PollOption selectedOption) {
        this.pollId = pollId;
        this.userId = userId;
        this.selectedOption = selectedOption;
    }

    public int getPollId() { return pollId; }
    public int getUserId() { return userId; }
    public PollOption getSelectedOption() { return selectedOption; }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


๐Ÿ“ Exceptions

package org.example.polling.exceptions;

public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) { super(message); }
}

public class PollNotFoundException extends RuntimeException {
    public PollNotFoundException(String message) { super(message); }
}

public class UnauthorizedActionException extends RuntimeException {
    public UnauthorizedActionException(String message) { super(message); }
}

public class InvalidOptionException extends RuntimeException {
    public InvalidOptionException(String message) { super(message); }
}

public class DuplicateVoteException extends RuntimeException {
    public DuplicateVoteException(String message) { super(message); }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top

๐Ÿ“ Repositories

UserRepository.java

package org.example.polling.repositories;

import org.example.polling.entities.User;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class UserRepository {
    private final Map<Integer, User> users = new ConcurrentHashMap<>();
    public void save(User user) { users.put(user.getUserId(), user); }
    public User findById(int userId) { return users.get(userId); }
    public boolean exists(int userId) { return users.containsKey(userId); }
}
Enter fullscreen mode Exit fullscreen mode

PollRepository.java

package org.example.polling.repositories;

import org.example.polling.entities.Poll;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class PollRepository {
    private final Map<Integer, Poll> polls = new ConcurrentHashMap<>();
    private final AtomicInteger pollCounter = new AtomicInteger(1);

    public Poll save(Poll poll) {
        polls.put(poll.getPollId(), poll);
        return poll;
    }

    public Poll findById(int pollId) { return polls.get(pollId); }
    public int generatePollId() { return pollCounter.getAndIncrement(); }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


VoteRepository.java

package org.example.polling.repositories;

import org.example.polling.entities.Vote;
import org.example.polling.enums.PollOption;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class VoteRepository {
    private final Map<Integer, Map<PollOption, AtomicInteger>> pollResults = new ConcurrentHashMap<>();
    private final Map<Integer, Map<Integer, PollOption>> userVotes = new ConcurrentHashMap<>();

    public void saveVote(Vote vote) {
        pollResults.putIfAbsent(vote.getPollId(), new ConcurrentHashMap<>());
        userVotes.putIfAbsent(vote.getPollId(), new ConcurrentHashMap<>());

        if (userVotes.get(vote.getPollId()).containsKey(vote.getUserId())) {
            throw new RuntimeException("User already voted in this poll!");
        }

        userVotes.get(vote.getPollId()).put(vote.getUserId(), vote.getSelectedOption());
        pollResults.get(vote.getPollId())
                .computeIfAbsent(vote.getSelectedOption(), k -> new AtomicInteger(0))
                .incrementAndGet();
    }

    public Map<PollOption, Integer> getResults(int pollId) {
        Map<PollOption, Integer> results = new ConcurrentHashMap<>();
        pollResults.getOrDefault(pollId, Map.of())
                .forEach((opt, count) -> results.put(opt, count.get()));
        return results;
    }

    public PollOption getUserVote(int pollId, int userId) {
        return userVotes.getOrDefault(pollId, Map.of()).get(userId);
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


๐Ÿ“ Services

PollService.java

package org.example.polling.services;

import org.example.polling.entities.Poll;
import org.example.polling.entities.User;
import org.example.polling.enums.PollOption;
import org.example.polling.enums.UserType;
import org.example.polling.exceptions.InvalidOptionException;
import org.example.polling.exceptions.PollNotFoundException;
import org.example.polling.exceptions.UnauthorizedActionException;
import org.example.polling.repositories.PollRepository;
import org.example.polling.repositories.UserRepository;
import org.example.polling.repositories.VoteRepository;
import java.util.*;

public class PollService {
    private final PollRepository pollRepository;
    private final VoteRepository voteRepository;
    private final UserRepository userRepository;

    public PollService(PollRepository pollRepository, VoteRepository voteRepository, UserRepository userRepository) {
        this.pollRepository = pollRepository;
        this.voteRepository = voteRepository;
        this.userRepository = userRepository;
    }

    public Poll createPoll(List<String> options, String question, User creator) {
        if (creator.getUserType() != UserType.ADMIN) {
            throw new UnauthorizedActionException("Only admins can create polls!");
        }

        Set<String> normalizedOptions = new LinkedHashSet<>();
        for (String option : options) {
            normalizedOptions.add(option.trim().toLowerCase());
        }

        if (normalizedOptions.size() < 2) {
            throw new InvalidOptionException("At least two unique options required!");
        }

        Map<PollOption, String> optionMap = new EnumMap<>(PollOption.class);
        int index = 0;
        for (String opt : normalizedOptions) {
            optionMap.put(PollOption.values()[index++], opt);
            if (index >= PollOption.values().length) break;
        }

        int pollId = pollRepository.generatePollId();
        Poll poll = new Poll(pollId, question, optionMap, creator);
        return pollRepository.save(poll);
    }

    public Map<PollOption, Integer> getResults(int pollId) {
        Poll poll = pollRepository.findById(pollId);
        if (poll == null) throw new PollNotFoundException("Poll not found!");
        return voteRepository.getResults(pollId);
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


UserService.java

package org.example.polling.services;

import org.example.polling.entities.User;
import org.example.polling.entities.Vote;
import org.example.polling.enums.PollOption;
import org.example.polling.exceptions.DuplicateVoteException;
import org.example.polling.exceptions.PollNotFoundException;
import org.example.polling.exceptions.UserNotFoundException;
import org.example.polling.repositories.PollRepository;
import org.example.polling.repositories.UserRepository;
import org.example.polling.repositories.VoteRepository;

public class UserService {
    private final PollRepository pollRepository;
    private final VoteRepository voteRepository;
    private final UserRepository userRepository;

    public UserService(PollRepository pollRepository, VoteRepository voteRepository, UserRepository userRepository) {
        this.pollRepository = pollRepository;
        this.voteRepository = voteRepository;
        this.userRepository = userRepository;
    }

    public void registerUser(User user) { userRepository.save(user); }

    public void castVote(Vote vote) {
        if (!userRepository.exists(vote.getUserId())) throw new UserNotFoundException("User does not exist!");
        if (pollRepository.findById(vote.getPollId()) == null) throw new PollNotFoundException("Poll does not exist!");
        if (voteRepository.getUserVote(vote.getPollId(), vote.getUserId()) != null)
            throw new DuplicateVoteException("User has already voted!");
        voteRepository.saveVote(vote);
    }

    public PollOption getUserVoteForPoll(int userId, int pollId) {
        return voteRepository.getUserVote(pollId, userId);
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


๐Ÿ“ Controllers

PollController.java

package org.example.polling.controllers;

import org.example.polling.entities.Poll;
import org.example.polling.entities.User;
import org.example.polling.enums.PollOption;
import org.example.polling.services.PollService;
import java.util.List;
import java.util.Map;

public class PollController {
    private final PollService pollService;
    public PollController(PollService pollService) { this.pollService = pollService; }
    public Poll createPoll(List<String> options, String question, User creator) {
        return pollService.createPoll(options, question, creator);
    }
    public Map<PollOption, Integer> getPollResults(int pollId) {
        return pollService.getResults(pollId);
    }
}
Enter fullscreen mode Exit fullscreen mode

UserController.java

package org.example.polling.controllers;

import org.example.polling.entities.Vote;
import org.example.polling.enums.PollOption;
import org.example.polling.services.UserService;

public class UserController {
    private final UserService userService;
    public UserController(UserService userService) { this.userService = userService; }
    public void castVote(Vote vote) { userService.castVote(vote); }
    public PollOption getUserVote(int userId, int pollId) {
        return userService.getUserVoteForPoll(userId, pollId);
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


๐Ÿ“ Main Runner

Main.java

package org.example.polling;

import org.example.polling.controllers.PollController;
import org.example.polling.controllers.UserController;
import org.example.polling.entities.Poll;
import org.example.polling.entities.User;
import org.example.polling.entities.Vote;
import org.example.polling.enums.PollOption;
import org.example.polling.enums.UserType;
import org.example.polling.repositories.PollRepository;
import org.example.polling.repositories.UserRepository;
import org.example.polling.repositories.VoteRepository;
import org.example.polling.services.PollService;
import org.example.polling.services.UserService;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        PollRepository pollRepository = new PollRepository();
        VoteRepository voteRepository = new VoteRepository();
        UserRepository userRepository = new UserRepository();

        PollService pollService = new PollService(pollRepository, voteRepository, userRepository);
        UserService userService = new UserService(pollRepository, voteRepository, userRepository);

        PollController pollController = new PollController(pollService);
        UserController userController = new UserController(userService);

        // Create admin
        User admin = new User(1001, "Admin", UserType.ADMIN);
        userService.registerUser(admin);

        // Create poll
        List<String> options = Arrays.asList("Apple", "Mango", "Banana", "Grapes");
        Poll poll = pollController.createPoll(options, "Which is your favorite option?", admin);

        // Register users
        User user1 = new User(1, "Alice", UserType.USER);
        User user2 = new User(2, "Bob", UserType.USER);
        User user3 = new User(3, "Charlie", UserType.USER);
        User user4 = new User(4, "Diana", UserType.USER);

        userService.registerUser(user1);
        userService.registerUser(user2);
        userService.registerUser(user3);
        userService.registerUser(user4);

        // Voting
        userController.castVote(new Vote(poll.getPollId(), user1.getUserId(), PollOption.OPTION_A));
        userController.castVote(new Vote(poll.getPollId(), user2.getUserId(), PollOption.OPTION_B));
        userController.castVote(new Vote(poll.getPollId(), user3.getUserId(), PollOption.OPTION_A));
        userController.castVote(new Vote(poll.getPollId(), user4.getUserId(), PollOption.OPTION_C));

        // Results
        System.out.println("Poll Results for Poll ID " + poll.getPollId() + ":");
        Map<PollOption, Integer> results = pollController.getPollResults(poll.getPollId());
        results.forEach((opt, count) -> System.out.println(opt + ": " + count));

        // User check
        System.out.println("\nUser 1 voted: " + userService.getUserVoteForPoll(user1.getUserId(), poll.getPollId()));
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


โ–ถ๏ธ Sample Execution

Output after running Main.java:

Poll Results for Poll ID 1:
OPTION_A: 2
OPTION_B: 1
OPTION_C: 1

User 1 voted: OPTION_A
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ผ Back to Top


๐Ÿš€ Future Improvements

  • Replace in-memory repositories with a database (JPA/Hibernate).
  • Add
  1. Polling System Basic Low-Level Design - I

  2. Low-Level Design: Polling System - Edge Cases

  3. Low-Level Design: Polling System - API's Using Nodejs & SQL

  4. Low-Level Design: Polling System - Edge Cases

  5. Low-Level Design: Polling System - In JAVA

More Details:

Get all articles related to system design
Hastag: SystemDesignWithZeeshanAli

Git: https://github.com/ZeeshanAli-0704/SystemDesignWithZeeshanAli

Top comments (0)