Computer Science Fundamentals · Foundations

Lists

Want it in plain words first? Jump to Eli explains — the same idea, no jargon.
On this page 9 sections
  1. In 30 seconds
  2. Why this matters
  3. The college version
  4. Eli explains
  5. Worked example
  6. Key takeaway
  7. Quick check
  8. Study tools
  9. Sources & references

In 30 seconds

A list is an abstract, ordered, resizable sequence of items you can index, insert into, and remove from. That same interface can be built two ways. A (Python's list, Java's ArrayList) stores items contiguously and grows itself: reading by index is O(1) and appending is amortized O(1), but inserting near the front is O(n). A chains nodes together with pointers: inserting or deleting at a you already hold is O(1), but reaching the i-th item takes O(n). Same idea, opposite strengths.

Why this matters

Almost every program keeps an ordered, growable collection of things, and 'list' is the name for that idea. Knowing that one list interface has two very different implementations lets you predict performance instead of being surprised by it: a loop that repeatedly inserts at the front of a Python list is quietly O(n^2). Data-structure courses build stacks, queues, hash tables, and graphs on top of these two layouts, and interviewers probe the distinction constantly. Choosing array-backed versus linked storage is one of the first real engineering trade-offs you will make on purpose: fast indexing against fast splicing.

The college version

A list is an interface, not a layout

A list is an abstract data type: an ordered, resizable sequence of items you reach one after another from a head toward a tail. What defines it is the set of operations it promises, not how those operations are stored in memory. The standard list interface offers get(i) and set(i, x) to read or overwrite the item at position i, add(i, x) to insert a new item at position i (shifting later items down), remove(i) to delete the item at position i, and a search to find where a value lives. 'Ordered' here means the items have positions 0, 1, 2, ... — first, second, third — not that they are sorted. Because a list is just this contract, two structures with completely different performance can both honor it. The rest of this lesson is about those two structures and why the same operation can be cheap on one and expensive on the other.

Implementation 1: the dynamic array

A dynamic array stores the items in one contiguous block of memory, exactly like a plain array, but keeps some spare capacity at the end and quietly allocates a bigger block when it fills up. Because storage is contiguous, the item at index i sits at a computable offset, so get(i) and set(i, x) are O(1) — direct random access, no scanning. (The base-plus-offset addressing math itself belongs to the arrays topic; here we only borrow the result that indexing is constant time.) Appending at the end is O(1) most of the time; occasionally the block is full and the structure allocates a larger one and copies every element over, which costs O(n) for that single append. Averaged over many appends this evens out to O(1) each — the amortized cost. The weakness is insertion and deletion away from the end: add(i, x) and remove(i) must shift every element after position i, which is O(n - i), and up to O(n) at the front. This is precisely what Python's built-in list is. In CPython, indexing and append are O(1), insert and delete are O(n), and 'x in mylist' is an O(n) linear search. The Python docs put it plainly: appends and pops at the end are fast, but inserts and pops at the beginning are slow because every other element must shift by one.

Implementation 2: the linked list

A linked list stores each item in its own small object called a node, and every node holds the value plus a pointer (a link) to the next node. The list keeps a head pointer to the first node; a singly-linked list links only forward, while a doubly-linked list also links backward and usually keeps a tail pointer to the last node. Nodes can sit anywhere in memory, so there is no offset arithmetic and therefore no O(1) indexing: to reach the i-th item you must start at the head and follow i links, which is O(n). Searching for a value is likewise O(n). The payoff is splicing. If you already hold a reference to a node, inserting a new node next to it or deleting it just rewires a couple of pointers — O(1), with nothing shifted. A doubly-linked list makes get, set, add, and remove run in O(1 + min{i, n-i}) time: the O(1) is the actual pointer work, and the min{i, n-i} is the cost of walking from the nearer end to find the node. Once you have the node, the edit is constant time. Appending is O(1) when a tail pointer is kept; without one, a singly-linked list must walk to the end first, which is O(n).

Comparing the two implementations

The two implementations are near mirror images. Reach for a dynamic array when you index or append a lot and rarely splice in the middle; reach for a linked list when you constantly insert and delete at positions you already have handles on and seldom jump to an arbitrary index. Here are the standard complexities:

OperationDynamic arrayLinked list
Access item at index iO(1)O(n)
Search for a valueO(n)O(n)
Insert/remove at the frontO(n)O(1)
Append at the endO(1) amortizedO(1) with a tail pointer, else O(n)
Insert/remove at a node you already holdO(n) (must shift)O(1)
Extra memory per elementsmall (spare capacity)one or two pointers per node

The deep reason the columns disagree is the memory model: contiguous storage buys O(1) indexing but makes middle edits expensive because of shifting; scattered nodes buy O(1) splicing but make indexing expensive because you must traverse. There is no free lunch — you trade indexing speed for splicing speed and back.

Terminology across languages

The word 'list' is overloaded, so name the implementation you mean. A Python 'list' and a Java 'ArrayList' are dynamic arrays, not linked lists — despite the name, indexing them is O(1) and front-insertion is O(n). Java also ships an actual 'LinkedList' (a doubly-linked list) with the opposite trade-offs. C and Java 'array' means the fixed-size, contiguous primitive that cannot grow — the resizable list is built on top of it. Lists are also the substrate for other structures you will meet soon: a stack (last-in, first-out) and a queue (first-in, first-out) are lists restricted to adding and removing at particular ends, and both can be backed by either a dynamic array or a linked list. When someone says 'use a list,' the useful follow-up question is always: array-backed or linked?

Eli, the EliExplains learning guide

Eli explains

The same idea, in plain words

Explain it like I’m 10

A list is just 'a bunch of things kept in order that you can add to and take from.' There are two ways to actually build it. One way is a row of numbered lockers all in a line: jumping straight to locker #500 is instant because you can count to its spot, but squeezing a new locker into the middle means sliding everyone else over. The other way is a treasure hunt: each clue points to where the next item is hidden. Adding an item is easy — you just change two clues to point at it — but to find the 500th item you have to follow 500 clues one at a time.

Picture it like this

Numbered lockers in a straight line are the dynamic array (jump anywhere instantly, but middle inserts shove everyone over). A treasure hunt where each clue points to the next spot is the linked list (easy to splice in a new clue, slow to reach a far one).

Where the picture stops working

The lockers analogy hides the resize cost: a real dynamic array occasionally runs out of lockers and has to move the whole row into a bigger hallway, which is the rare expensive append. And the treasure-hunt clues are free to picture but not free in memory — every real node spends extra space storing its pointer, which the analogy doesn't show.

Worked example

Suppose a list holds [10, 20, 30, 40] and we insert 15 at the front, then read the item at index 3. As a dynamic array, inserting at the front means shifting 10, 20, 30, 40 each one slot right to open position 0, then placing 15 — four moves, i.e. O(n) work — giving [15, 10, 20, 30, 40]; reading index 3 is then a single computed lookup returning 30, O(1). As a linked list, inserting 15 at the head just makes a new node point to the old first node and moves the head pointer — O(1), nothing shifts. But reading index 3 now costs a walk: head to node 0, 1, 2, 3, following four links to reach 30, i.e. O(n). Same two operations, and each structure is fast on exactly the one the other is slow on.

Key takeaway

A list is one abstract ordered sequence with two common implementations: the dynamic array (O(1) index, amortized O(1) append, O(n) front insert) and the linked list (O(1) insert/delete at a held node, O(n) index and search). Pick by whether you index more or splice more.

Quick check

3 questions here, of 5 in this lesson’s practice set. Answers stay hidden until you check.

Question 1 of 3foundational

A Python "list" is implemented internally as which data structure?

Choose an answer, then check it.
Question 2 of 3intermediate

Which operation is O(1) on a dynamic array but O(n) on a linked list?

Choose an answer, then check it.
Question 3 of 3intermediate

In a linked list, what makes it possible to insert a new node between two existing nodes in O(1) time?

Choose an answer, then check it.
Practice all 5

Keep learning

Ready to build on this? Continue to the next lesson.

Practice this lesson
Study tools & related lessonsYou’ll learn to · Common mistakes · Easily confused · Key vocabulary · Related

You’ll learn to

  • Define a list as an abstract, ordered, resizable sequence, separate from any one implementation.
  • Distinguish a dynamic array from a linked list by how each stores and connects its elements.
  • Explain why array-backed index access is O(1) while linked-list index access is O(n).
  • Explain what 'amortized O(1)' means for appending to a dynamic array.
  • Apply the operation-complexity trade-offs to choose an implementation for a given access pattern.

Common mistakes

  • Believing a Python 'list' is a linked list because of the name.

    A Python list is a dynamic array. Indexing it is O(1) and appending is amortized O(1); inserting at the front is O(n) because elements shift. Java's ArrayList is the same idea; Java's LinkedList is the actual linked list.

  • Claiming a linked list makes all inserts O(1).

    Insert/delete is O(1) only when you already hold a reference to the relevant node. If you must first find the position by index or value, that search is O(n), and only then is the pointer edit O(1).

  • Thinking appending to a dynamic array is O(n) because it sometimes copies everything.

    Only the occasional resize copies everything. Spread over many appends the total is O(n) for n appends, so each append is amortized O(1).

  • Assuming a linked list is always more memory-efficient because it 'grows as needed.'

    Each node carries one or two extra pointers, so a linked list usually uses more memory per element than a dynamic array, which stores bare values plus a little spare capacity.

  • Expecting O(1) random access from a linked list.

    Reaching the i-th element requires following links from an end, which is O(n). If you index frequently, use an array-backed list instead.

Easily confused

Dynamic array vs. Linked list

Contiguous storage gives the array O(1) indexing but O(n) middle inserts (shifting); scattered nodes give the linked list O(1) splicing at a held node but O(n) indexing (traversal).

List (abstract type) vs. Array (fixed primitive)

A list is an abstract resizable sequence that can be array-backed or linked; a primitive array is a fixed-size contiguous block that cannot grow — the dynamic array is built on top of it.

Amortized O(1) append (dynamic array) vs. True O(1) insert at head (linked list)

The array's append is O(1) only on average, with rare O(n) resizes; the linked list's head insert is O(1) every single time, but that structure gives up O(1) indexing to get it.

Key vocabulary

List (abstract data type)
An ordered, resizable sequence of items reached from a head toward a tail, defined by the operations it supports (get, set, insert, remove, search) rather than by how it is stored.
Dynamic array
A list implementation that keeps items in a contiguous block with spare capacity, allocating a larger block and copying over when it fills; gives O(1) indexing and amortized O(1) append.
Linked list
A list implementation in which each item lives in a node that holds a pointer to the next node; gives O(1) insert/delete at a held node but O(n) access by position.
Node
The small object a linked list uses for one element: it stores the value together with a pointer (link) to the next node, and in a doubly-linked list a pointer to the previous node too.
Index (random access)
Retrieving the element at a given position i directly. Contiguous storage makes this O(1); pointer-chained storage makes it O(n).
Amortized time
The average cost of an operation across a long run, used when most calls are cheap but a few are expensive; a dynamic array's append is amortized O(1) because rare O(n) resizes are spread over many O(1) appends.
Contiguous memory
Storage in one unbroken block, so an element's location can be computed from its index; the basis for a dynamic array's constant-time indexing.
Head / tail pointer
A reference to the first node (head) or last node (tail) of a linked list; a tail pointer is what lets appends be O(1) instead of O(n).

Sources & references

  1. list — NIST Dictionary of Algorithms and Data Structures — National Institute of Standards and Technology (NIST)
  2. linked list — NIST Dictionary of Algorithms and Data Structures — National Institute of Standards and Technology (NIST)
  3. Open Data Structures, Chapter 2: Array-Based Lists (ArrayStack) — Pat Morin / opendatastructures.org
  4. Open Data Structures, 3.2: DLList — A Doubly-Linked List — Pat Morin / opendatastructures.org
  5. TimeComplexity — Python Wiki — Python Software Foundation (python.org wiki)
  6. 5. Data Structures — The Python Standard Library Documentation — Python Software Foundation

EliExplains lessons are original prose written from the open, credible references above. See Copyright & Licensing.

Researched 2026-08-19

Educational content only. It is not medical, legal or professional advice. Found an error? Tell us.