Efficiency of grouping a List of case classes by more than one field
Your code is fine

There's nothing wrong with the way you've implemented it. It's readable, and will have reasonable O(n) performance.

Alternative implementation

You've mentioned l is created by concatenation of two lists. As far as I understood one of the lists contains only clients with bool=false and one only clients with bool=true. Some clients will appear on both lists. Example:

val l1 = List(
  Client("87658763", Some(Instant.EPOCH), false),
  Client("87658769", Some(Instant.EPOCH), false),
)

val l2 = List(
  Client("87658769", Some(Instant.EPOCH), true)
)


If this is correct, then an alternative way to implement you function is:

val m1 = l1.map(c =&gt; (c.key, c.time) -&gt; c).toMap
val m2 = l2.map(c =&gt; (c.key, c.time) -&gt; c).toMap

(m1 ++ m2).values.toList


Or for better (most likely) performance you can use the breakOut trick which will do the mapping and List to Map transformation in single step.

val m1: Map[(String, Option[Instant]), Client] = l1.map(c =&gt; (c.key, c.time) -&gt; c)(scala.collection.breakOut)
val m2: Map[(String, Option[Instant]), Client] = l2.map(c =&gt; (c.key, c.time) -&gt; c)(scala.collection.breakOut)

(m1 ++ m2).values.toList


Whether this solution is more elegant than your's is subjective. I leave it up to you to decide which one you like more. Whether it's faster should be determined by measurement under production-like load.

First measure &amp; profile

First of all, if you're doing these operations once per the pair of mentioned API calls, then it's likely that the performance will get dominated by the API calls, not this code. If you're doing it more often, then you could remember the results. Before optimising the code I recommend you to measure the performance and determine where is the bottleneck.

Performance factors

If you come to a conclusion that performance of this code is indeed critical, then most likely the problem will be one of these:


allocation of garbage - groupBy, map, toList each allocate a new data structure.  No, the Scala compiler does not optimise this in any way. While allocation is a cheap process, all the garbage will have to be collected, and Garbage Collection can be expensive in some scenarios.
passes over the data set - groupBy, map, toList each is an O(n) operation iterating over the data. Additional these operations may slow down if the data won't easily fit in CPUs cache.


Which one will be dominant, will depend on Heap and GC configuration, the size of l, other workloads in the JVM and other factors. Both can be improved by doing as small number of passes over the data as possible.

Less passes over the data

These are the hints I can give on reducing the number of passes over the data, in this, and other similar situations:


Check If you can avoid some collection conversions. Maybe the Iterable[Client] returned by my is ok, and you don't have to convert it toList. 
Use breakOut.   
Try doing more in single step. foldLeft is a powerful operation which lets you combine many operations on a collection into one (at the expense of readability).    
Use mutable structures under the hood. Keep immutables in the API.