Question: I currently have a list of dictionaries that looks like that: total_list = [ {'email': 'usera@email.com', 'id': 1, 'country': 'UK'}, {'email': 'usera@email.com', 'id': 1, 'country': 'Germany'}, {'email': 'userb@email.com', 'id': 2, 'country': 'UK'} {'email': 'userc@email.com', 'id': 3, 'country': 'Italy'}, {'email': 'userc@email.com', 'id': 3, 'country': 'Netherland'}, {'email': 'userd@email.com', 'id': 4, 'country': 'France'}, ... ]  I want to split it primarily based on size, so let's say that the new size list is 3 items per list, But I also want to make sure that all the same users will be in the same new sublist. So the result I am trying to create is: list_a = [ {'email': 'usera@email.com', 'id': 1, 'country': 'UK'}, {'email': 'userb@email.com', 'id': 2, 'country': 'UK'} {'email': 'usera@email.com', 'id': 1, 'country': 'Germany'} ] list_b = [ {'email': 'userc@email.com', 'id': 3, 'country': 'Italy'}, {'email': 'userd@email.com', 'id': 4, 'country': 'France'} {'email': 'userc@email.com', 'id': 3, 'country': 'Netherland'}, ... ]  Obviously in the example that I provided the users were located really close to each other in the list, but in reality, they could be spread way more. I was considering sorting the list based on the email and then splitting them, but I am not sure what happens if the items that are supposed to be grouped together happen to be at the exact location that the main list will be divided. What I have tried so far is: def list_splitter(main_list, size): for i in range(0, len(main_list), size): yield main_list[i:i + size] # calculating the needed number of sublists max_per_batch = 3 number_of_sublists = ceil(len(total_list) / max_per_batch) # sort the data by email total_list.sort(key=lambda x: x['email']) sublists = list(list_splitter(main_list=total_list, size=max_per_batch))  The issue is that with this logic I cannot 100% ensure that if there are any items with the same email value they will end up in the same sublist. Because of the sorting, chances are that this will happen, but it is not certain. Basically, I need a method to make sure that items with the same email will always be in the same sublist, but the main condition of the split is the sublist size.

Answer: This solution starts of by only working with the list of all emails. The emails are then grouped based on their frequency and the limit on group size. Later the remaining data, i.e. id and country, are joined back on the email groups. The first function create_groups works on the list of emails. It counts the number of occurrences of each email and groups them. Each new group starts with the most frequent email. If there is room left in the group it looks for the most frequent that also fits in the group. If such an item exists, it is added to the group. This is repeated until the group is full; then, a new group is started. from operator import itemgetter from itertools import groupby, chain from collections import Counter def create_groups(items, group_size_limit): # Count the frequency of all items and create a list of items # sorted by descending frequency items_not_grouped = Counter(items).most_common() groups = [] while items_not_grouped: # Start a new group with the most frequent ungrouped item item, count = items_not_grouped.pop(0) group, group_size = [item], count while group_size < group_size_limit: # If there is room left in the group, look for a new group member for index, (candidate, candidate_count) \ in enumerate(items_not_grouped): if candidate_count <= group_size_limit - group_size: # If the candidate fits, add it to the group group.append(candidate) group_size += candidate_count # ... and remove it from the items not grouped items_not_grouped.pop(index) break else: # If the for loop did not break, no items fit in the group break groups.append(group) return groups  This is the result of using that function on your example: users = [ {'email': 'usera@email.com', 'id': 1, 'country': 'UK',}, {'email': 'userb@email.com', 'id': 2, 'country': 'UK'}, {'email': 'usera@email.com', 'id': 1, 'country': 'Germany'}, {'email': 'userc@email.com', 'id': 3, 'country': 'Italy'}, {'email': 'userd@email.com', 'id': 4, 'country': 'France'}, {'email': 'userc@email.com', 'id': 3, 'country': 'Netherland'} ] emails = [user["email"] for user in users] email_groups = create_groups(emails, 3) # -> [ # ['usera@email.com', 'userb@email.com'], # ['userc@email.com', 'userd@email.com'] # ]  Finally, when the groups have been created, the function join_data_on_groups groups the original dictionary of users. It takes the email-groups from before and the list of dictionaries as arguments: def join_data_on_groups(groups, item_to_data): item_to_data = {item: list(data) for item, data in item_to_data} groups = [(item_to_data[item] for item in group) for group in groups] groups = [list(chain(*group)) for group in groups] return groups email_getter = itemgetter("email") users_grouped_by_email = groupby(sorted(users, key=email_getter), email_getter) user_groups = join_data_on_groups(email_groups, users_grouped_by_email) print(user_groups)  Result: [ [ {'email': 'usera@email.com', 'id': 1, 'country': 'UK'}, {'email': 'usera@email.com', 'id': 1, 'country': 'Germany'}, {'email': 'userb@email.com', 'id': 2, 'country': 'UK'} ], [ {'email': 'userc@email.com', 'id': 3, 'country': 'Italy'}, {'email': 'userc@email.com', 'id': 3, 'country': 'Netherland'}, {'email': 'userd@email.com', 'id': 4, 'country': 'France'} ] ]