Code challenge: Simple analytics
Here are the types that we're working with:
interface User {
subscription: 'NONE' | 'BASIC' | 'PREMIUM';
username: string
}
type SubscriptionTypeCounts = Record<User['subscription'], number>
type GetSubscriptionTypeCounts =
(users: Array<User>) => SubscriptionTypeCounts
Approach #1: Functional with .reduce()
An example of how you can tackle this is by using the Array.prototype.reduce function to loop through the array of users and increment a counter of each subscription type on each iteration.
const getSubscriptionTypeCounts: GetSubscriptionTypeCounts = (users) => {
return users.reduce((result, user) => {
return { ...result, [user.subscription]: result[subscription] + 1 };
}, { NONE: 0, BASIC: 0, PREMIUM: 0 });
}
This approach is written in a functional way. A new object is created on each iteration with the values from the previous one, and incrementing the count for the subscription type by 1. The benefit of this approach is that it's pure, immutable and concise. The downsides are that if you're looping through a very large array, performance may be slightly lower than modifying the values "in place" and that it's using a modern Object spread syntax that some developers may not be familiar with yet or supported by older browsers.
Approach #2: Imperative with .reduce()
Another version of using a similar reduce function, but updating the existing object on each iteration.
const getSubscriptionTypeCounts: GetSubscriptionTypeCounts = (users) => {
return users.reduce((result, user) => {
result[subscription] = result[subscription] + 1;
return result;
};
}, { NONE: 0, BASIC: 0, PREMIUM: 0 });
}
This is almost identical to the previous example, but may be slightly easier for junior developers to read, slightly more performant and has more browser compatibility than the previous example.
Approach #3: For loop
Another approach could be a standard for
loop.
const getSubscriptionTypeCounts: GetSubscriptionTypeCounts = (users) => {
const result = { NONE: 0, BASIC: 0, PREMIUM: 0 };
for (let i = 0; i < users.length; i++) {
const subscription = users[i].subscription;
result[subscription] = result[subscription] + 1;
}
return result;
}
Most developers learn for
loops first, so it's arguably more readable for a wider audience. It also updates the values in place for a slight performance boost and uses classic syntax with strong browser compatibility.
Conclusion
In the real world, the small fraction of performance boost from modifying in place is rarely needed and it's easy to shoot yourself in the foot with more complex examples that we'll discuss in later posts. There's also a great course on how objects work and all of their quirks in JavaScript on Dan Abramov's Just JavaScript course.
There's no right answer here, but for what it's worth, I'd use the first approach based on my personal preference of writing functional-style code with pure immutable functions.
Happy coding! SL