Code challenge: Simple analytics

Write a function that takes an input of users and returns the count of each subscription type.

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

Subscribe to Sean W. Lawrence

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe