The dozen facts behind most NLTK errors — all confirmed on a live nltk 3.10.0 install.
01It ships no data. Almost every call needs nltk.download(...) first — the LookupError tells you which resource.
02Names changed in 3.9+. punkt_tab not punkt; averaged_perceptron_tagger_eng not ..._tagger. Stale sheets break here.
03The data changes type at each step: str → list[str] → list[tuple] → Tree. Pass the right shape.
04pos_tag takes a list, not a string. Tokenize first, or every character gets tagged.
05Lemmatizer defaults to noun. lemmatize('running') → 'running'; lemmatize('running','v') → 'run'.
06Stems aren't words ('studies'→'studi'); lemmas are. Stem to index, lemmatize to read.
07Tags are Penn Treebank by default (NN, VBD, JJ). Add tagset='universal' for simple ones.
08Lowercase before stopword filtering — the list is all lowercase, or 'The' slips through.
09bigrams/ngrams are generators — wrap in list() to see or reuse them.
10WordNet POS letters: n v a r — adverb is r, not "adv".
11FreqDist is a dict. Use .most_common(n); indexing missing keys gives 0, not KeyError.
12NLTK teaches; spaCy ships. Reach for spaCy/🤗 when you need speed or transformers.