The canonical way to sort a Python dictionary by value is sorted(my_dict.items(), key=lambda kv: kv[1]). Pass reverse=True for descending order. Wrap the result in dict() if you want a dictionary back. Two upgrades most tutorials skip: operator.itemgetter(1) replaces the lambda and runs faster, and for "top-N" use cases heapq.nlargest(n, ...) is dramatically quicker than sorting the whole thing. One trap to avoid: dict(sorted(...)) gives you a dict with keys in sorted order at that moment, but the next time you add or change a key the order breaks. For a structure that stays sorted, you need a sorted list of tuples or a third-party SortedDict. This piece is one of 17 short explainers in our Python Concepts Explained reference.
Key Takeaways
Canonical form:sorted(d.items(), key=lambda kv: kv[1]) returns a list of (key, value) tuples in ascending order. reverse=True flips to descending; wrap in dict() for a dict result.
Faster idiom:operator.itemgetter(1) replaces the lambda. Roughly 20-30% quicker and reads cleaner: sorted(d.items(), key=itemgetter(1)).
Top-N pattern:heapq.nlargest(n, d.items(), key=itemgetter(1)) beats sorted()[:n] for large dicts. O(n log k) vs O(n log n) means 50-100× faster for top-10 from a million entries.
Stable sort means tied items keep insertion order. For explicit tiebreakers, use a tuple key: key=lambda kv: (kv[1], kv[0]) sorts by value, then by key.
dict(sorted(...)) is a one-shot snapshot. Dicts iterate in insertion order; adding a new key puts it at the end. For a persistent sorted structure, use sortedcontainers.SortedDict or a sorted list of tuples.
insertion order, not sorted order.
For a structure that stays sorted, use SortedDict or a sorted list of tuples.Four branches: pick the answer that matches what you actually need. The bottom warning catches the most common production bug.
Three pieces. scores.items() turns the dict into (key, value) pairs. sorted() sorts any iterable, returning a list. The key function tells sorted what to compare; lambda kv: kv[1] says "compare by the second element of each tuple", which is the value.
Getting a dict back
If you need the result as a dictionary, wrap in dict():
Since Python 3.7, dicts preserve insertion order (for the full story see our dict iteration explainer). So this gives you a dict whose iteration order matches the sort. The catch, covered below, is that this is a one-shot snapshot.
List of tuples vs dict
The list-of-tuples form is often more useful than a dict, because it:
Lets you index into a specific rank: sorted_pairs[0] for the smallest, sorted_pairs[-1] for the largest.
Plays well with slicing: sorted_pairs[:3] for the bottom three.
Doesn't pretend to be sorted forever; the list IS sorted because that's what sorted() returns.
Reach for the dict form only when downstream code specifically needs dict lookup; otherwise stick with the list.
operator.itemgetter: Faster and Cleaner
The lambda works, but Python ships with a better tool: operator.itemgetter.
Speed.itemgetter is written in C. The lambda is a Python-level callable that pays per-call interpreter overhead. On a million-item dict, itemgetter is roughly 20-30% faster (numbers in the performance table below).
Readability.key=itemgetter(1) reads as "use index 1 as the sort key" instead of "use a function that returns index 1 of its argument".
Composability.itemgetter can grab multiple indices: itemgetter(1, 0) returns (value, key) tuples, which is how you sort by value then by key in one step.
The lambda form remains valid and very common; reach for itemgetter in production code, mature codebases, and any time the sort key is a simple field access.
Ascending vs Descending
The default is ascending. For descending, pass reverse=True:
This works but has two downsides. It only works for numeric values (you can't negate a string), and it's noisier than the explicit reverse=True. Use reverse=True; reserve the negation trick for multi-key sorts where you want mixed directions in one key tuple.
heapq.nlargest for Top-N
If you only want the top few items, sorting the whole dictionary is overkill. heapq.nlargest finds the n largest items in O(n log k) time, where k is how many you want. For top-10 from a million-item dict, that's roughly 50-100× faster than sorted().
import heapq
from operator import itemgetter
big_dict = {f"user{i}": i for i in range(1_000_000)}
# Top-10 by value
top10 = heapq.nlargest(10, big_dict.items(), key=itemgetter(1))
# [('user999999', 999999), ('user999998', 999998), ...]
# Bottom-10 by value
bottom10 = heapq.nsmallest(10, big_dict.items(), key=itemgetter(1))
# [('user0', 0), ('user1', 1), ...]
The complexity math: sorted() sorts everything (n log n), then you slice off the first n. heapq.nlargest maintains a heap of size n while scanning the input once, so it never sorts the whole input. The bigger the dict and the smaller the n, the bigger the win.
When sorted() is fine
If you want most of the dict in order (say, top 80%), sorted()[:n] is fine; you'd have done most of the work anyway. If you want exactly one item, max() / min() with key= is the right tool. The sweet spot for heapq.nlargest is "much less than the dict's size".
Tie-Breaking and Stable Sort
Python's sort has been stable since the start, meaning ties keep their original relative order. For dict items, that's insertion order:
data = {"alice": 80, "bob": 80, "charlie": 70}
sorted(data.items(), key=itemgetter(1))
# [('charlie', 70), ('alice', 80), ('bob', 80)]
# ^ alice before bob because she was inserted first
If you want a specific tiebreaker, build a tuple key. The tuple is compared element by element; the second element only matters when the first ties.
# Sort by value ascending, then by key alphabetical
sorted(data.items(), key=lambda kv: (kv[1], kv[0]))
# [('charlie', 70), ('alice', 80), ('bob', 80)]
# Sort by value descending, then by key ascending (mixed directions)
sorted(data.items(), key=lambda kv: (-kv[1], kv[0]))
# [('alice', 80), ('bob', 80), ('charlie', 70)]
The mixed-direction pattern is exactly where the "negate the numeric key" trick earns its keep: you can't pass two separate reverse flags, but you can put a minus sign in one position of a tuple.
The "Sorted Dict" Anti-Pattern
The most common production bug in this area. dict(sorted(d.items(), key=...)) gives you a dict whose keys iterate in sorted order at that moment. But dicts iterate in insertion order. The next mutation breaks the sort:
sorted_d = dict(sorted(scores.items(), key=itemgetter(1)))
# {'charlie': 70, 'bob': 80, 'alice': 95}
sorted_d["diana"] = 30
# {'charlie': 70, 'bob': 80, 'alice': 95, 'diana': 30}
# diana lands at the END, not in sorted position!
sorted_d["bob"] = 100
# {'charlie': 70, 'bob': 100, 'alice': 95, 'diana': 30}
# bob's value changed but the key stays where it was, breaking the order
If you need a structure that stays sorted as you mutate, you have two clean options.
Option 1: sorted list of tuples with bisect.insort
Keep sorted(d.items(), key=...) as a list and use bisect.insort to insert new entries in their correct position in O(log n + n) time (the lookup is logarithmic; the shift is linear, since lists need to move elements aside).
The key parameter on bisect.insort arrived in Python 3.10. Before that, you'd decorate manually. For small-to-medium dicts where reads and writes interleave, this approach beats re-sorting every time without pulling in a third-party dependency. For thousands of inserts per second, SortedDict or SortedList from sortedcontainers uses skip-list-like structures that give O(log n) for both reads and writes.
Option 2: sortedcontainers.SortedDict
SortedContainers (pip install sortedcontainers) is a battle-tested third-party library by Grant Jenks. SortedDict maintains keys in sorted order automatically and offers dict-like access:
from sortedcontainers import SortedDict
sd = SortedDict({"alice": 95, "bob": 80, "charlie": 70})
sd["diana"] = 30
# SortedDict({'alice': 95, 'bob': 80, 'charlie': 70, 'diana': 30})
# Keys stay in sorted order — alphabetical by default
Note: SortedDict sorts by KEY, not by value. To stay sorted by value, store value as the key (in a (value, key) tuple) or use a custom SortedList of tuples. There's no built-in "dict sorted by value that stays sorted" type in any standard or popular library, because the data structure is inherently awkward (updating a value rearranges its position).
Rule: if you only need to read the sorted result once, sorted(d.items(), key=...) is the answer. If the dict mutates after sorting and you need the order to follow, you're in "keep a sorted list of tuples" territory; don't try to make a dict pretend to be sorted.
Sort by Key
The same machinery sorts by key. Just drop the key argument (since each tuple's natural sort order is key-first), or use itemgetter(0) for clarity:
# Default sort: by key (alphabetical for strings)
sorted(scores.items())
# [('alice', 95), ('bob', 80), ('charlie', 70)]
# Explicit
sorted(scores.items(), key=itemgetter(0))
A common shorter form when you only need the keys themselves:
sorted(scores) # ['alice', 'bob', 'charlie']
sorted(scores.keys()) # same thing
Use sorted(d) for "give me the keys in order"; use sorted(d.items()) when you need the pairs.
Multi-Key Sort: by Value, Then by Key
For dictionaries with many ties, an explicit tiebreaker by key gives you a deterministic order:
The itemgetter(1, 0) form returns (value, key) tuples, which sort naturally by first then second component. Cleaner than the lambda.
Sorting Nested Dict Values
Real-world dicts often hold dicts as values. {"alice": {"score": 95, "age": 30}, "bob": {"score": 80, "age": 25}} is closer to most application data than the flat {"alice": 95} examples. The sort pattern only changes inside the key function:
users = {
"alice": {"score": 95, "age": 30},
"bob": {"score": 80, "age": 25},
"charlie": {"score": 70, "age": 35},
}
# Sort by inner score
sorted(users.items(), key=lambda kv: kv[1]["score"])
# [('charlie', {...70...}), ('bob', {...80...}), ('alice', {...95...})]
# Sort by inner score descending, then by age ascending as tiebreaker
sorted(users.items(), key=lambda kv: (-kv[1]["score"], kv[1]["age"]))
itemgetter also chains through layers via the slightly less-known attrgetter for objects, but for dict-of-dicts the lambda form is usually clearer. Reach for itemgetter when the access pattern is a simple top-level field; reach for a lambda the moment you need to dot or bracket into nested structure.
The Decoration Pattern: When the Key Function Is Expensive
If the sort key requires real computation per item (a database lookup, a regex, a network call), the lambda calls it once per comparison, which means O(n log n) times. The fix is to decorate the data first, sort by the precomputed key, then strip the decoration. The pattern is called the Schwartzian transform in older Perl-era literature:
from operator import itemgetter
def expensive_score(user):
# imagine a DB call or heavy computation
...
# Naive: expensive_score runs O(n log n) times
sorted(users.items(), key=lambda kv: expensive_score(kv[0]))
# Decoration pattern: expensive_score runs exactly n times
decorated = [(expensive_score(k), k, v) for k, v in users.items()]
decorated.sort(key=itemgetter(0))
result = [(k, v) for _, k, v in decorated]
The savings grow with the cost of the key function. For a 1,000-item dict where expensive_score takes 1 ms, naive sort spends ~10 seconds in the key function alone; the decoration pattern finishes in 1 second. For trivial key functions, the decoration overhead isn't worth it; reach for this pattern when the key computation actually hurts.
Performance Comparison
Rough timings on a 100,000-entry dict (CPython 3.12, single-threaded):
Method
Time
Notes
sorted(d.items(), key=lambda kv: kv[1])
~18 ms
baseline
sorted(d.items(), key=itemgetter(1))
~14 ms
~25% faster than lambda
heapq.nlargest(10, d.items(), key=itemgetter(1))
~3 ms
~6× faster than sorted for top-10
max(d, key=d.get)
~1 ms
single biggest, single pass
dict(sorted(d.items(), key=...))
~19 ms
sort + dict construction
The picture changes with input size. At 1,000 items everything is fast and the differences disappear; at 10 million items the heapq.nlargest advantage grows to 50-100×. Pick the method by the shape of the input, not the abstract complexity.
Worked Example: Word-Frequency Top-10
The classic "sort a dict by value" use case in production is "what are the top-N most frequent items in this stream?" Combining collections.Counter, heapq.nlargest, and the patterns above gives you the canonical Python answer in three lines.
from collections import Counter
import heapq
from operator import itemgetter
text = "the quick brown fox jumps over the lazy dog the the the"
counts = Counter(text.split())
# Counter({'the': 5, 'quick': 1, 'brown': 1, 'fox': 1, ...})
# Built-in: Counter has most_common(n) for exactly this case
top3 = counts.most_common(3)
# [('the', 5), ('quick', 1), ('brown', 1)]
# General form using heapq (works on any dict, not just Counter)
top3_general = heapq.nlargest(3, counts.items(), key=itemgetter(1))
Counter.most_common(n) is the specialized fast path; internally it uses heapq.nlargest when n is small relative to the total. For counting specifically, prefer Counter; for general dicts, prefer the heapq.nlargest form directly. Both finish in microseconds even on million-element inputs.
Common Mistakes
Five traps:Mistake 1: forgetting to call .items().sorted(my_dict, key=...) iterates the dict's keys, so your key function receives keys, not pairs. Use sorted(my_dict.items(), ...) whenever you want both halves.Mistake 2: trying to sort in place. Dicts don't have a .sort() method. sorted() returns a new sorted list, leaving the original dict untouched. There is no in-place option, by design.Mistake 3: expecting dict(sorted(...)) to stay sorted. Covered above. The result is a one-shot snapshot; mutations append to the end.Mistake 4: using sorted() when you only need top-N.
Switch to heapq.nlargest the moment N is much smaller than the dict size. The complexity win shows up at scale.Mistake 5: forgetting that max(d) and min(d) ignore values.max(scores) returns the largest key by alphabetical order, not the key with the largest value. Pass key=scores.get to compare by value: max(scores, key=scores.get).
Frequently Asked Questions
How do I sort a Python dictionary by value?
Pass the dict items to sorted() with a key function that returns the value: sorted(my_dict.items(), key=lambda kv: kv[1]). The result is a list of (key, value) tuples in ascending order. Pass reverse=True for descending. Wrap the result in dict() if you want a dict back. For cleaner code, operator.itemgetter(1) replaces the lambda and runs slightly faster.
Is operator.itemgetter faster than lambda for sorting?
Yes, by roughly 20 to 30 percent on typical inputs. itemgetter is written in C and avoids the Python-level function call overhead that lambda pays per comparison. For small dicts the difference is irrelevant, but on a million-item dict it adds up. The bigger win is readability: from operator import itemgetter; sorted(d.items(), key=itemgetter(1)) reads cleaner than the lambda form.
How do I get the top N items from a Python dictionary by value?
Use heapq.nlargest(n, my_dict.items(), key=operator.itemgetter(1)) instead of sorted()[:n]. heapq runs in O(n log k) time where k is the number you want, while sorted runs in O(n log n) over the full input. For top-10 from a million-item dict, heapq is roughly 50 to 100 times faster. heapq.nsmallest does the same for the bottom-N.
What happens when two values are equal during sorting?
Python's sort is stable. Tied items keep their original relative order, which for a dict means their insertion order. To force a specific tiebreaker, use a tuple as the sort key: key=lambda kv: (kv[1], kv[0]) sorts by value, then by key alphabetically. For mixed ascending and descending in the same sort, negate the numeric part: key=lambda kv: (-kv[1], kv[0]) gives values descending, keys ascending.
Does dict(sorted(...)) stay sorted after adding new keys?
No. dict(sorted(my_dict.items(), ...)) gives you a dict with keys in sorted order at the moment of creation, but Python dicts iterate in insertion order. The next time you add or change a key, it lands at the end, breaking the sorted property. For a structure that stays sorted automatically, use sortedcontainers.SortedDict (third-party) or keep a sorted list of tuples.
The Bottom Line: Pick the Method by What You Actually Need
The canonical lambda answer is fine, but mature Python code reaches further. Use operator.itemgetter(1) for the cleaner, faster sort key. Use heapq.nlargest when you only want the top-N from a big dict. Use a tuple sort key for explicit tiebreakers. And remember the production trap: dict(sorted(...)) looks like a sorted dict but stops being one the moment you mutate it. For a structure that stays sorted, you need a sorted list of tuples or a dedicated sorted-collection library. For the rest of the most-asked Python concept questions, browse the full Python Concepts Explained index.
Drill Sort Patterns Until They're Reflex
CodeGym's Python track turns sort idioms into muscle memory through 800+ hands-on tasks across 62 levels. The AI validator checks every submission in seconds; the AI mentor explains what broke when you get stuck. First level free; full plan on the pricing page.
Start learning Python (free) →
GO TO FULL VERSION