QuerySet lazy nghĩa là Django chưa chạy SQL khi bạn tạo query, chỉ chạy khi evaluate — tức là iterate, list(), len(), bool(), slicing có step, hoặc serialize.
Nhờ vậy bạn có thể .filter().exclude().order_by() nối nhiều bước mà cuối cùng vẫn chỉ chạy đúng một câu SQL.
qs = Post.objects.filter(published=True) # chưa hit DB
qs = qs.order_by('-created_at')[:10] # vẫn chưa hit
for p in qs: # ← hit DB ở đây
print(p.title)Bug hay gặp nhất: gọi qs.count() trước rồi for p in qs → 2 query, vì count() chạy SELECT COUNT(*) riêng và không điền result cache (còn len(qs) thì có — nó evaluate và cache nên chỉ 1 query). Sửa đơn giản: posts = list(qs) rồi dùng len(posts) cho cả 2 việc.
Một điểm tinh tế hay vấp: QuerySet có cache kết quả trong cùng instance sau lần evaluate đầu, nhưng nếu bạn .filter() thêm thì đó là QuerySet mới — cache không tồn tại nữa. Cần tái sử dụng kết quả thì gán biến và evaluate một lần.