Skip to content

Instantly share code, notes, and snippets.

@aaiezza
Last active November 24, 2021 20:06
Show Gist options
  • Save aaiezza/a0f110ec8e4bd0a928c3be363c7c9334 to your computer and use it in GitHub Desktop.
Save aaiezza/a0f110ec8e4bd0a928c3be363c7c9334 to your computer and use it in GitHub Desktop.
Calculate 401K employer match based on a tiered-contribution-matching system
package challenge;
import static challenge.EmployerRetirementContributionCalculator.EmployeeContribution.employeeContribution;
import static challenge.EmployerRetirementContributionCalculator.EmployerContribution.Tier.Percentage.REMAINING_AMOUNT;
import challenge.EmployerRetirementContributionCalculator.EmployerContribution.Amount;
import challenge.EmployerRetirementContributionCalculator.EmployerContribution.EmployeeContributionMatched;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/*
# 401k contribution, employer would match.
#
# for the first 3% (of the salary) employee contribution percentage, employer match 100% of the contribution;
# for the next 2% (of the salary) contribution percentage, employer match 50% of the contribution;
# for the remaining contribution percentage, match 25% of the contribution; employer will cap the match to $4000.
contributing 10% of his salary -> the salary is $1000
first 3% -> $30
next 2% -> $10
remaining 5% -> $12.5
# 1 data model to capture this employer match data
# 2 for you to implement a generic method to calculate the match an employee will get, taking employee's salary, 401k contrinution percentage, and the match model as inputs.
*/
class EmployerRetirementContributionCalculator {
@lombok.Value
public static class Salary {
private final double value;
}
@lombok.Value
public static class EmployeeContribution {
private final double value;
public static EmployeeContribution employeeContribution(
final Salary salary, final ContributionPercentage contributionPercentage) {
return new EmployeeContribution(salary.getValue() * contributionPercentage.getValue());
}
}
@lombok.Value
public static class ContributionPercentage {
private final double value;
}
@lombok.Value
public static class EmployerContribution {
private final Amount amount;
private final EmployeeContributionMatched amountMatched;
public EmployerContribution applyTier(
final Tier tier, final Salary salary, final EmployeeContribution employeeContribution) {
final var tierAmountToMatch = salary.getValue() * tier.getPercentage().getValue();
final var amountAvailableToMatch =
new EmployeeContributionMatched(
Math.min(
employeeContribution.getValue() - getAmountMatched().getValue(),
tierAmountToMatch));
return new EmployerContribution(
new Amount(
getAmount().getValue()
+ (amountAvailableToMatch.getValue() * tier.getMatchPercentage().getValue())),
new EmployeeContributionMatched(
getAmountMatched().getValue() + amountAvailableToMatch.getValue()));
}
@lombok.Value
public static class Amount {
private final double value;
}
@lombok.Value
public static class EmployeeContributionMatched {
private final double value;
}
@lombok.Value
public static class Tier {
private final Percentage percentage;
private final MatchPercentage matchPercentage;
public Tier withPercentageRemainingFromTiers(final List<Tier> tiers) {
return new Tier(
new Percentage(
tiers.stream().map(Tier::getPercentage).mapToDouble(Percentage::getValue).sum()),
this.matchPercentage);
}
public boolean tierAppliesToRemainingAmountOfEmployeeContribution() {
return getPercentage() == REMAINING_AMOUNT;
}
@lombok.Value
public static class Percentage {
public static final Percentage REMAINING_AMOUNT = new Percentage(0);
private final double value;
}
@lombok.Value
public static class MatchPercentage {
private final double value;
}
}
}
public EmployerContribution calculateMatch(
final Salary salary,
final ContributionPercentage contributionPercentage,
final List<EmployerContribution.Tier> tiers) {
final var employeeContribution = employeeContribution(salary, contributionPercentage);
final AtomicReference<EmployerContribution> employerContribution =
new AtomicReference<>(
new EmployerContribution(new Amount(0), new EmployeeContributionMatched(0)));
tiers.stream()
.map(
tier ->
tier.tierAppliesToRemainingAmountOfEmployeeContribution()
? tier.withPercentageRemainingFromTiers(tiers)
: tier)
.forEach(
tier ->
employerContribution.set(
employerContribution.get().applyTier(tier, salary, employeeContribution)));
return employerContribution.get();
}
}
package challenge;
import static challenge.EmployerRetirementContributionCalculator.EmployerContribution.Tier.Percentage.REMAINING_AMOUNT;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import challenge.EmployerRetirementContributionCalculator.ContributionPercentage;
import challenge.EmployerRetirementContributionCalculator.EmployerContribution;
import challenge.EmployerRetirementContributionCalculator.EmployerContribution.Tier;
import challenge.EmployerRetirementContributionCalculator.EmployerContribution.Tier.MatchPercentage;
import challenge.EmployerRetirementContributionCalculator.EmployerContribution.Tier.Percentage;
import challenge.EmployerRetirementContributionCalculator.Salary;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class EmployerRetirementContributionCalculatorTest {
private EmployerRetirementContributionCalculator subject;
@BeforeEach
void setUp() {
subject = new EmployerRetirementContributionCalculator();
}
@MethodSource
@ParameterizedTest(name = "[{index}] {0} : Returns {4}")
void shouldCalculateExpectedEmployerContribution(
final String description,
final Salary salary,
final ContributionPercentage contributionPercentage,
final List<EmployerContribution.Tier> tiers,
final EmployerContribution.Amount expectedMatchAmount) {
assertThat(subject.calculateMatch(salary, contributionPercentage, tiers).getAmount())
.isEqualTo(expectedMatchAmount);
}
static List<Arguments> shouldCalculateExpectedEmployerContribution() {
return Stream.of(
arguments(
"$1000 * 10% | tiers (3%→100%), (2%→50%), (x%→25%)",
new Salary(1000.00),
new ContributionPercentage(.1),
asList(
new Tier(new Percentage(.03), new MatchPercentage(1.0)),
new Tier(new Percentage(.02), new MatchPercentage(.5)),
new Tier(REMAINING_AMOUNT, new MatchPercentage(.25))),
new EmployerContribution.Amount(52.5)),
arguments(
"$1000 * 1% | tiers (3%→100%), (2%→50%), (x%→25%)",
new Salary(1000.00),
new ContributionPercentage(.01),
asList(
new Tier(new Percentage(.03), new MatchPercentage(1.0)),
new Tier(new Percentage(.02), new MatchPercentage(.5)),
new Tier(REMAINING_AMOUNT, new MatchPercentage(.25))),
new EmployerContribution.Amount(10.0)),
arguments(
"$152,000 * 15% | tier (4%→100%)",
new Salary(152000.00),
new ContributionPercentage(.15),
asList(new Tier(new Percentage(.04), new MatchPercentage(1.0))),
new EmployerContribution.Amount(6080.00)),
arguments(
"$1,000 * 1% | no employer match",
new Salary(1000.00),
new ContributionPercentage(.1),
asList(),
new EmployerContribution.Amount(0)))
.collect(toList());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment