def outer_product(l):
    """Produces the outer product of a list of lists.
    
    Example:
    >>> l = [[1, 2], [3, 4]]
    >>> outer_product(l) == [[1, 3], [1, 4], [2, 3], [2, 4]]
    
    If the number of lists is fixed, it is better to use a list comprehension
    >>> [(e1, e2) for e1 in l1 for e2 in l2]
    """
    def _expand(l, pos, result):
        """Recurses on `l` and produces all combinations of elements from the lists."""
        for e in l[pos]:
            result.append(e)
            if pos == len(l) - 1:
                yield result[:]
            else:
                for r in _expand(l, pos + 1, result):
                    yield r
            result.pop()
    return _expand(l, 0, [])