How to Sort Python Dictionaries by Key or Value?

1. Why Sorting Dictionaries Matters in Real-World Code

“It’s not always about what data you have. Sometimes, it’s about how cleanly and predictably you can present it.”

Let me tell you upfront — I don’t sort dictionaries just for the fun of it. When you’re deep in a pipeline, cleaning data at scale or prepping it for display in a dashboard, the order of items can quickly go from irrelevant to absolutely essential.

I’ve personally run into this during model debugging. For instance, I was trying to log a series of hyperparameter settings during a grid search.

Without sorting, the output was all over the place. The same dictionary printed differently every time — not ideal when you’re comparing across runs. Sorting by key made the logs clean and deterministic.

Another case? Preprocessing inputs for a recommender system. I had to sort items by score before feeding them into a downstream component that expected a ranked list. That’s not just aesthetics — that’s logic.

Now, here’s something that might save you a headache: before Python 3.7, dicts didn’t guarantee insertion order. So if you’re working with legacy code or switching environments, keep that in mind.

Personally, I’ve been burned by this when something worked locally but broke in prod — turned out the production environment was using Python 3.6. Lesson learned.

Sorting dictionaries isn’t just a trick to make things pretty. It’s about predictability. It’s about control.

In the next section, I’ll show you exactly how I handle sorting by key — in different flavors — depending on whether I need a copy or want to overwrite the original. Stick around; I’ve got some clean patterns for that.


2. Sorting by Keys (Strings, Ints, or Anything Hashable)

Let’s be honest — most of the time, when I’m sorting a dictionary by keys, it’s because I care about readability or predictable ordering, especially for debugging or logging. I’ve lost count of how many times a simple sorted view helped me quickly spot misconfigurations or inconsistent values in pipeline configs or experiment metadata.

2.1 Sorted Copy Using sorted()

Here’s the deal: if you want a new dictionary that’s sorted by key (without touching the original), this is the pattern I personally keep reaching for:

my_dict = {
    "banana": 3,
    "apple": 4,
    "cherry": 2
}

# Sorted by key (returns a new dict)
sorted_by_key = {k: my_dict[k] for k in sorted(my_dict)}
print(sorted_by_key)

Output:

{'apple': 4, 'banana': 3, 'cherry': 2}

This comes in especially handy when I’m dealing with config dicts or JSON-style payloads that need to be ordered alphabetically for comparison between environments.

Now, if your keys are integers, same approach:

int_keyed_dict = {
    20: 'twenty',
    5: 'five',
    13: 'thirteen'
}

sorted_int_keys = {k: int_keyed_dict[k] for k in sorted(int_keyed_dict)}
print(sorted_int_keys)

Output:

{5: 'five', 13: 'thirteen', 20: 'twenty'}

Pro Tip from the trenches: You’ll want to avoid this if your dictionary has mixed-type keys — like strings and integers together. Python will throw a TypeError because it can’t compare them. I’ve hit this in real-world ETL tasks when working with malformed JSON blobs or legacy APIs that weren’t strict about key types. Sanitize your keys upfront or skip sorting altogether in those cases.

2.2 In-Place Alternative (Overwriting with Sorted Version)

You might be wondering: Can I just sort the dictionary in place?

Technically, no — because dictionaries in Python are unordered mappings by design (in terms of API), even though they preserve insertion order since Python 3.7. But you can absolutely overwrite the original with a sorted version. I’ve done this countless times when I need the dict to behave deterministically in subsequent steps.

Here’s what that looks like:

config = {
    "zeta": 0.01,
    "alpha": 0.3,
    "gamma": 0.7
}

# Overwrite with sorted copy
config = dict(sorted(config.items()))
print(config)

Output:

{'alpha': 0.3, 'gamma': 0.7, 'zeta': 0.01}

I’ve used this trick when generating reproducible config files for model experiments — especially when I’m dumping them to YAML or JSON. It ensures consistency across runs, which is crucial if you’re validating experiment diffs or reviewing logs in a CI pipeline.


3. Sorting by Values (Single-Level Dictionaries)

“Sometimes the key doesn’t matter. It’s what’s inside that counts.”

This part hits closer to home — especially when I’m working with metrics, ranking scores, or aggregated stats. If you’ve ever needed to pick the top 5 models based on accuracy or sort user activity by engagement level, you’ve already faced this.

Let’s jump straight into the patterns I use when sorting dictionaries by value.

3.1 Sorted Copy by Value

You might be wondering: How do I sort a dictionary by its values — not the keys?

This is where sorted(d.items(), key=lambda x: x[1]) becomes your best friend. I use this all the time when preparing reports or dashboards where values drive the narrative.

Example – Sorting numerics (ascending & descending):

scores = {
    "model_a": 0.89,
    "model_b": 0.95,
    "model_c": 0.83
}

# Sort ascending (lowest to highest)
sorted_by_value = dict(sorted(scores.items(), key=lambda x: x[1]))
print(sorted_by_value)

# Sort descending (highest to lowest)
sorted_desc = dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))
print(sorted_desc)

Output:

{'model_c': 0.83, 'model_a': 0.89, 'model_b': 0.95}
{'model_b': 0.95, 'model_a': 0.89, 'model_c': 0.83}

I’ve used this pattern in leaderboard systems for internal model benchmarks. It makes it ridiculously easy to show “best performing first” and keeps the output clean and understandable — especially when you’re piping this into a template or log.

You can do the same thing for string values:

users = {
    "user_1": "Zane",
    "user_2": "Alice",
    "user_3": "Mark"
}

sorted_by_name = dict(sorted(users.items(), key=lambda x: x[1]))
print(sorted_by_name)

Output:

{'user_2': 'Alice', 'user_3': 'Mark', 'user_1': 'Zane'}

This one’s perfect for sorting dropdowns, UI-facing APIs, or even when auto-generating admin reports — something I’ve done while building internal tooling.

3.2 Custom Value-Based Sorting

Here’s where things get more real. It’s not always simple values. A lot of times, I’m dealing with nested structures — especially when values are themselves dicts or tuples. Sorting these by a specific field? Super common.

Say you’ve got this:

models = {
    "xgboost": {"score": 0.87, "params": 120},
    "random_forest": {"score": 0.91, "params": 240},
    "logistic": {"score": 0.78, "params": 30}
}

You want to sort by 'score'. Here’s exactly what I do:

sorted_models = dict(sorted(models.items(), key=lambda x: x[1]['score'], reverse=True))
print(sorted_models)

Output:

{
 'random_forest': {'score': 0.91, 'params': 240},
 'xgboost': {'score': 0.87, 'params': 120},
 'logistic': {'score': 0.78, 'params': 30}
}

This pattern saved me tons of time while building a batch evaluation pipeline — I just had to dump the top models into an output file, sorted by accuracy, and this gave me exactly what I needed in one line.

Another flavor: values are tuples, and you want to sort by the first item in the tuple:

metrics = {
    "run_001": (0.93, "2024-11-02"),
    "run_002": (0.87, "2024-11-01"),
    "run_003": (0.95, "2024-11-03")
}

# Sort by score (first item in tuple)
sorted_runs = dict(sorted(metrics.items(), key=lambda x: x[1][0], reverse=True))
print(sorted_runs)

I’ve used this exact pattern while analyzing experiment history stored in flat logs. Fast, readable, and easy to tweak — which matters when you’re sifting through hundreds of runs.


4. Sorting Dictionaries with Lambda Functions and Custom Criteria

“Not all dictionaries are born equal — some hide their gold deeper.”

I’ve lost count of how many times I’ve needed to sort dictionaries where the values aren’t just flat numbers or strings, but nested structures — sometimes dicts, sometimes objects, sometimes a Frankenstein tuple-dict combo from an API response.

Here’s the deal: when you’re working with complex structures, lambda becomes your scalpel — precise, customizable, and fast to deploy.

Example 1: Dict of dicts — sort by nested key

Let’s say you’re working with user metadata stored like this:

users = {
    "user_1": {"name": "Alice", "age": 34},
    "user_2": {"name": "Bob", "age": 28},
    "user_3": {"name": "Charlie", "age": 40}
}

You want to sort this by age (ascending):

sorted_users = dict(sorted(users.items(), key=lambda x: x[1]["age"]))
print(sorted_users)

Output:

{
 'user_2': {'name': 'Bob', 'age': 28},
 'user_1': {'name': 'Alice', 'age': 34},
 'user_3': {'name': 'Charlie', 'age': 40}
}

Personally, I’ve used this in real-time user dashboards where metadata like timestamps, scores, or activity counts live inside nested dicts. It’s clean, readable, and scales well unless you’re working with really deep nesting.

Example 2: Using operator.itemgetter — for better readability

This might surprise you: if you’re sorting by a single known key, itemgetter can sometimes edge out lambda in both clarity and micro-performance (especially in tight loops).

from operator import itemgetter

sorted_users = dict(sorted(users.items(), key=lambda x: itemgetter("age")(x[1])))
print(sorted_users)

Or even better, extract the nested dicts first:

user_values = list(users.items())
sorted_users = dict(sorted(user_values, key=lambda x: x[1]["age"]))

Tiny note on performance:

If you’re in a performance-critical loop, and especially if sorting is part of a batch processing pipeline, itemgetter has slightly better performance because it avoids creating a new lambda function each iteration. That said, for most day-to-day work, the difference is negligible — readability wins.


5. Maintaining Order: SortedDict and OrderedDict (When and Why)

You might be wondering: Why bother with OrderedDict now that regular dicts preserve order?

Here’s my take — from experience:

When I still reach for OrderedDict:

  • I’m writing code that interacts with older libraries or APIs that explicitly expect it.
  • I need to reorder items and retain full control over insert position.
  • I’m serializing to JSON and want the fields to appear in a specific, predictable order.

Let me show you exactly what I mean.

from collections import OrderedDict

# Same structure as regular dict, but maintains insertion order (Python 3.6 and earlier)
ordered = OrderedDict()
ordered["z"] = 1
ordered["a"] = 2
ordered["m"] = 3

print(ordered)

Output:

OrderedDict([('z', 1), ('a', 2), ('m', 3)])

What about SortedDict?

This one’s not in the standard lib — it comes from sortedcontainers, and I’ve used it when I need a consistently sorted view of the data at all times.

from sortedcontainers import SortedDict

sorted_dict = SortedDict({
    "banana": 3,
    "apple": 5,
    "cherry": 2
})

print(sorted_dict)

Output:

SortedDict({'apple': 5, 'banana': 3, 'cherry': 2})

This is insanely useful when you’re working with rolling windows, sliding metrics, or any logic where order isn’t just nice-to-have — it’s part of the algorithm. I’ve used it for time-series bucketization and windowed aggregations where dict keys represent timestamps.

Final note:

If you’re on Python 3.7+, you probably don’t need OrderedDict for normal code. But when you do — you’ll know. And when you’re dealing with sorted views on constantly mutating structures? That’s where SortedDict saves the day.


6. Sorting Nested Dictionaries

“A flat dictionary is easy. But real-world data? It’s rarely flat — it’s layered like baklava.”

I’ve run into this a lot when dealing with API responses or deeply nested config files. You’ve got a dictionary, but what you really care about is something buried two or three layers deep — maybe a score, a timestamp, or a priority level.

Let’s look at a practical example.

data = {
    "item1": {"meta": {"score": 88}},
    "item2": {"meta": {"score": 95}},
    "item3": {"meta": {"score": 76}}
}

You want to sort this by the nested "score" field.

sorted_data = dict(sorted(data.items(), key=lambda x: x[1]["meta"]["score"]))
print(sorted_data)

Output:

{
 'item3': {'meta': {'score': 76}},
 'item1': {'meta': {'score': 88}},
 'item2': {'meta': {'score': 95}}
}

Here’s the deal: this works great when you know the nested structure is consistent. But if you’re working with patchy data — and I mean real-world, “50% of keys missing stuff” kind of data — you need to play it safe.

Handling Missing Keys Gracefully

This is something I’ve had to do especially when working with loosely validated JSON from third-party APIs.

sorted_data = dict(sorted(
    data.items(),
    key=lambda x: x[1].get("meta", {}).get("score", float('-inf'))
))

Or, if you want to go full belt-and-suspenders:

def safe_get_score(entry):
    try:
        return entry[1]["meta"]["score"]
    except (KeyError, TypeError):
        return float('-inf')  # or 0, or None, depending on context

sorted_data = dict(sorted(data.items(), key=safe_get_score))

Personally, I’ve found this pattern useful not just in logging, but also in data quality validation. You’ll often catch issues here before they bubble up downstream in modeling or reporting layers.


7. Sorting for Display vs Sorting for Logic

This might seem subtle, but it’s a big one — and it’s tripped up more than a few devs I’ve worked with.

There’s a difference between sorting to see data vs sorting to use it.

Let me show you both.

Example 1: Sorting for Display (without modifying original)

Say you want to print something out, maybe for a debug log or a terminal-friendly preview:

for k, v in sorted(data.items(), key=lambda x: x[1]["meta"]["score"]):
    print(f"{k}: {v}")

In this case, you’re not touching the original structure — just displaying a sorted view. I do this when I’m scanning logs or prepping a quick console check.

Example 2: Sorting for Logic (before passing to model/API)

Now, let’s say your model expects data sorted by score (e.g., top-N selection logic).

sorted_data = dict(sorted(data.items(), key=lambda x: x[1]["meta"]["score"], reverse=True))

# Top 2 entries based on score
top_2 = dict(list(sorted_data.items())[:2])

This kind of sorting is critical when your logic depends on order — like prioritization, selection, or rate-limiting strategies. I’ve used this exact pattern when prepping candidate pools for recommendation engines or reranking steps in a pipeline.

Pro tip: Always be explicit in your intent. If you’re sorting for display, leave the source untouched. If you’re sorting for downstream logic, make it clear and document that it’s essential to the correctness of the flow.


8. Performance Considerations

“Fast is fine, but accuracy is everything. In this case, we want both.” – Wyatt Earp, probably not talking about sorted(), but still relevant.

This might surprise you, but sorted(d.items()) can sneak up on you with performance costs when you’re dealing with massive dictionaries — especially when you’re sorting inside loops or pipelines that run frequently.

I’ve benchmarked this before while optimizing a scoring system that refreshed rankings every few seconds. Here’s a quick synthetic test to show what I mean:

import timeit
import random

data = {f'key_{i}': random.randint(1, 1000000) for i in range(100000)}
stmt = "dict(sorted(data.items(), key=lambda x: x[1]))"

print(timeit.timeit(stmt, globals=globals(), number=10))

On my machine, this ran around 0.3 to 0.5 seconds per call for 100K entries. Not a disaster — but if you’re doing this repeatedly in a tight loop, it’s a red flag.

When You Should Avoid Sorting Altogether

If you only need the top-N values, skip the full sort. Seriously — don’t waste cycles.

Instead, use a heap:

import heapq

top_10 = heapq.nlargest(10, data.items(), key=lambda x: x[1])

I’ve used this approach when working on anomaly detection pipelines, where I only cared about the top-K high scorers per batch. It’s a big win when your dataset grows into the millions.


9. Idiomatic Patterns I Keep Reusing

This section is basically a confession. There are certain snippets I’ve reused so many times across projects that I’ve half-joked about putting them on a t-shirt.

Here are my go-to patterns for sorting dictionaries. You’ll see these show up in recsys logic, monitoring dashboards, and even ETL cleanup code.

Sorting by Nested Score Field

sorted_items = dict(sorted(data.items(), key=lambda x: x[1]["meta"]["score"]))

Any time I’m dealing with model outputs or complex scoring logic, this is where I start.

Sorting by Length of String Values

sorted_by_length = dict(sorted(data.items(), key=lambda x: len(x[1])))

This has helped me with things like ranking tags or filtering overly verbose labels. Simple but effective.

Sorting by Parsed Datetime Values

from datetime import datetime

data = {
    "log1": "2024-06-15T12:00:00Z",
    "log2": "2023-11-01T08:30:00Z",
    "log3": "2024-01-20T15:45:00Z"
}

sorted_logs = dict(sorted(
    data.items(),
    key=lambda x: datetime.fromisoformat(x[1].replace("Z", "+00:00"))
))

When dealing with logs, events, or job history — this is gold. I’ve leaned on this in audit trails, cron job dashboards, and anywhere timestamps are key to understanding flow.

Final Thought

You might be wondering: why share this real-world stuff instead of just sticking to clean examples?

Because this is what actually survives in production. Theories and syntax come and go, but these idioms are what I’ve carried across companies, side projects, and client work.


10. Wrap-Up

Let’s make this part short, sharp, and straight to the point — like a good lambda function.

Over the years, I’ve found that how you sort dictionaries matters less than why you’re doing it. That context drives everything — from whether you need a sorted copy, to whether you can skip sorting entirely and reach for a heap or just keep things unordered.

Here’s a quick breakdown to keep in your back pocket:

ScenarioMethod
Need a sorted copy for reporting, logging, or light transformationdict(sorted(d.items()))
Want to overwrite with a sorted versionSame as above — just reassign
Need to sort by value (e.g., scores, counts)key=lambda x: x[1]
Sorting nested structureskey=lambda x: x[1]['some_key'] or itemgetter()
Want top-N, not full sortUse heapq.nlargest() — avoid sorting entirely
Sorting for display/debugging onlyNever mutate — just print the sorted view
Working with legacy APIs or needing predictable order pre-3.7Use OrderedDict or SortedDict
Sorting needed before batch inference, rank-based filtering, or model inputDo it early in your pipeline — not as a patch later on

Final Note From Experience

When you’re building complex data flows — like ranking pipelines, custom aggregators, or inference batches — it’s easy to treat sorting as an afterthought. But I’ve found that putting sorting upfront in your pipeline design pays dividends. It makes downstream logic cleaner, safer, and often faster to debug.

Sorting isn’t just a data transformation. It’s a signal: you’re making a decision about what matters most in the data.

Leave a Comment