# The Tail End

> Push, pop, peek. The basics that break people.

Canonical URL: <https://datadriven.io/problems/the_tail_end>

Domain: Python · Difficulty: easy · Seniority: L3

## Problem

Implement a stack via an operations list. Each operation is a list: ['push', x], ['pop'], ['peek'], ['is_empty']. Return a parallel list of return values: None for push, the popped value (or None if empty) for pop, the top value (or None if empty) for peek, True/False for is_empty.

## Worked solution and explanation

### Why this problem exists in real interviews

This tests **class design** and the **stack abstract data type**. Implementing a stack from scratch probes understanding of LIFO semantics, encapsulation, and defensive method behavior for edge cases.

---

### Break down the requirements

#### Step 1: Use a list as the backing store

Python lists provide O(1) append and pop from the end, making them ideal stack backing structures.

#### Step 2: Implement push, pop, peek, and is_empty

Push appends to the end. Pop removes and returns the last element. Peek returns it without removal. All must handle the empty case.

#### Step 3: Return None for pop and peek on empty stack

Instead of raising an error, return None as specified.

---

### The solution

**List-backed stack with None sentinel**

```python
class Stack:
    def __init__(self):
        self.items = []
    def push(self, val):
        self.items.append(val)
    def pop(self):
        if not self.items:
            return None
        return self.items.pop()
    def peek(self):
        if not self.items:
            return None
        return self.items[-1]
    def is_empty(self):
        result = len(self.items) == 0
        return result
```

> **Time and Space Complexity**
>
> **Time:** O(1) for all operations. List append and pop from end are amortized O(1).
> 
> **Space:** O(n) where n is the number of elements on the stack.

> **Interviewers Watch For**
>
> The choice to return None vs. raise an exception. The prompt specifies None, but strong candidates mention that exceptions are often preferred in production to catch bugs early.

> **Common Pitfall**
>
> Using `self.items[0]` for peek or `self.items.pop(0)` for pop. These operate on the wrong end and give queue behavior (FIFO) instead of stack behavior (LIFO).

---

## Common follow-up questions

- How would you add a min() method that returns the minimum in O(1)? _(Tests the min-stack pattern using a parallel stack of minimums.)_
- What if the stack needed a maximum size limit? _(Tests adding a capacity check in push and deciding what to do on overflow.)_
- How would you implement a stack using two queues? _(Tests understanding the queue-to-stack conversion, a classic data structure exercise.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/the_tail_end)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). 100% free data engineering interview prep. Live code execution against Postgres 16, Python 3.11, and Spark sandboxes. No paywall, no premium tier, no signup gate.