Part 1

First question

d = {'a': 1,
     'b': 2,
     'a': 3}  # <-- repeated 'a' key

Counting words

fd2 = {}
for word in words_to_count:
    if not word in fd2:
        fd2[word] = 0
    fd2[word] += 1

or

fd2 = {}
for word in words_to_count:
    if not word in fd2:
        fd2[word] = 1
    else:
        fd2[word] += 1

or

fd2 = {}
for word in words_to_count:
    fd2[word] = fd2.get(word, 0) + 1

or

fd2 = {}
for word in words_to_count:
    fd2.setdefault(word, 0)
    fd2[word] += 1

etc.

Part 2

is_empty()

def is_empty(x):
    if x:
        return False
    else:
        return True
def is_empty(x):
    return len(x) == 0
def is_empty(x):
    if x==[] or x=={}:  # <-- overfits to the test cases
        return True
    else:
        return False

word_lengths()

Original:

def word_lengths(words):
    """Return a list of word lengths for each word in words."""
    if len(words) > 0:
        for word in words:
            return len(word)  # <-- return inside loop
    # <-- no 'else' case, returns `None` for an empty list

Minimal changes:

def word_lengths(words):
    """Return a list of word lengths for each word in words."""
    lengths = []
    if len(words) > 0:        # <-- if-test is unnecessary
        for word in words:
            lengths.append(len(word))
    return lengths

Simpler:

def word_lengths(words):
    """Return a list of word lengths for each word in words."""
    return [len(word) for word in words]

Observed changes:

def word_lengths(words):
    """Return a list of word lengths for each word in words."""
    if len(words) > 0:
        for word in words:
            return [len(word) for word in words]  # <-- return still inside loop!
    else:
        return []  # <-- also: return words (subtle bug!)