A linked list is a sequence of nodes, each holding a value and a reference to the next node. A doubly linked list also holds a reference to the previous node. Nodes can live anywhere in memory; the list "exists" only via the chain of pointers.
Costs
Random access is — to reach index you must walk pointers. Insertion or deletion is when you already have a pointer to the affected node; otherwise it's to find that node first. Appending to the tail is only if you maintain a tail pointer.
Why we use them anyway
Three reasons: (1) no resizing — you never copy the whole structure as it grows; (2) constant-time splicing of nodes in the middle, which arrays can't match; (3) memory efficiency in the sense that you allocate exactly what you need (though per-node pointer overhead can dominate for small values).
Doubly vs singly
Doubly linked lists let you walk backward and delete a node given only that node (without needing its predecessor). They're the standard backing store for LRU caches and most language-built-in lists (Java LinkedList, C++ std::list). Singly is enough for stacks, simple queues, and chained hash tables.
Sentinel nodes
A common technique to simplify code: add a dummy "head" sentinel before the first real node, and a dummy "tail" sentinel after the last. Now every insertion and deletion has neighbors on both sides — no special case for "first" or "last" node. Halves the bug surface in production list code.
When you SHOULDN'T use one
Most of the time. Modern CPU caches favor arrays. The textbook example of "insert in the middle in " assumes you already have a pointer to that node — finding the node is , which dominates. If you're choosing between a Python list and a linked list, choose the list.