Streamlit Multipage Apps - Interview Questions
1. How do you build a multipage app in Streamlit?
Two ways — pick one:
Modern (Streamlit ≥ 1.36): st.navigation + st.Page — explicit, code-defined, supports dynamic pages.
# app.py
import streamlit as st
pages = [
st.Page("home.py", title="Home", icon=":material/home:"),
st.Page("dashboard.py", title="Dashboard"),
st.Page("admin.py", title="Admin"),
]
pg = st.navigation(pages)
pg.run()
Legacy (still supported): pages/ directory. Drop scripts into a pages/ folder next to your main script:
app.py
pages/
1__Dashboard.py
2__Settings.py
Streamlit auto-discovers them and builds a sidebar nav. Filenames become page titles (numeric prefix sets order, emoji becomes icon).
For new apps prefer the modern API — it gives you control over auth-gated pages, dynamic page lists, and grouped navigation.
2. When should you split into multiple pages vs. one page with tabs?
| Use multiple pages when | Use tabs when |
|---|---|
| Sections are independent and don’t share heavy state | Sections are facets of the same data |
| You want bookmarkable URLs per page | Single URL is fine |
| Each section is its own concern (admin, user, settings) | All views relate to one entity |
| Heavy code per section — splitting helps readability | Lightweight switching |
Tabs (st.tabs) re-render together and don’t change the URL. Pages have separate URLs and only run the active page’s script.
3. How do you share state across pages?
st.session_state is shared across all pages within a session. No extra setup:
# home.py
st.session_state["user"] = login_form()
# dashboard.py
user = st.session_state.get("user")
if user is None:
st.warning("Please log in on the Home page.")
st.stop()
Cache decorators (@st.cache_data, @st.cache_resource) are also shared across pages — a model loaded on one page is available on all others.
What is not shared: local variables in each page’s script. Each page’s script runs from the top when navigated to.
4. How do you make pages conditional (auth-gated, role-based)?
With st.navigation, build the page list dynamically:
pages = [st.Page("home.py", title="Home")]
if st.session_state.get("logged_in"):
pages += [
st.Page("dashboard.py", title="Dashboard"),
st.Page("settings.py", title="Settings"),
]
if st.session_state.get("is_admin"):
pages += [st.Page("admin.py", title="Admin")]
st.navigation(pages).run()
This is the main reason to prefer st.navigation over the pages/ directory: you decide which pages exist per user.
For the legacy pages/ approach, gate at the top of each page:
if not st.session_state.get("is_admin"):
st.error("Forbidden")
st.stop()
5. How do page URLs and query parameters work?
# Read query params from URL like ?user=42&tab=stats
params = st.query_params
user_id = params.get("user") # "42"
# Set / mutate
st.query_params["tab"] = "stats"
st.query_params.clear() # remove all
# Build a deep link
url = f"/dashboard?user={user_id}"
st.link_button("Open dashboard", url)
st.query_params is dict-like. Setting a key updates the browser URL without reloading. Use it for bookmarkable filters, shareable links, and deep navigation.
Pages have URLs like /Dashboard (from page title). With st.Page, set url_path="dashboard" for control.
6. How do you switch pages programmatically?
if st.button("Go to dashboard"):
st.switch_page("dashboard.py") # legacy pages/
# or with st.Page objects:
st.switch_page(dashboard_page)
st.switch_page interrupts the current run and renders the target page. Useful after login: redirect to the home page once authenticated.
7. Where do you put shared code (utilities, models, DB)?
A normal Python module next to the entrypoint, imported from each page:
app.py
db.py # connection helpers, queries
models.py # pydantic models
auth.py # login, role checks
pages/
1_dashboard.py
2_admin.py
# pages/1_dashboard.py
import streamlit as st
from db import get_engine
from auth import require_login
require_login()
engine = get_engine()
...
Cache resources (DB engines, models) at module level with @st.cache_resource so each page gets the same instance:
# db.py
import streamlit as st
from sqlalchemy import create_engine
@st.cache_resource
def get_engine():
return create_engine(st.secrets["DATABASE_URL"])
8. How do you customize the sidebar / nav?
With st.navigation, group pages by section:
pages = {
"Account": [
st.Page("home.py", title="Home"),
st.Page("profile.py", title="Profile"),
],
"Reports": [
st.Page("daily.py", title="Daily"),
st.Page("monthly.py", title="Monthly"),
],
}
st.navigation(pages).run()
Position with st.navigation(pages, position="sidebar") (default) or position="hidden" (you build the nav yourself with buttons + st.switch_page).
Set the page-wide config once, in the entrypoint:
st.set_page_config(
page_title="MyApp",
page_icon=":material/dashboard:",
layout="wide",
initial_sidebar_state="expanded",
)
st.set_page_config must be the first Streamlit call in the script.
9. Common multipage pitfalls
| Pitfall | Fix |
|---|---|
| State lost when switching pages | It isn’t — local vars reset, st.session_state persists. Check you’re using state, not locals |
| Cache cleared on navigation | It isn’t. If you see this, something is calling .clear() |
st.set_page_config errors on a sub-page |
Only call it once, in the entrypoint |
| Auth gate added on every page is duplicated | Extract to auth.require_login() and call at the top of each page; or build a dynamic page list with st.navigation |
| URLs change between dev and prod due to title-based routing | Set explicit url_path on each st.Page |
| Heavy imports rerun on every page nav | Move them into cached factory functions; module-level imports run once per process per page module |
10. Cross-links
- Sharing state across pages: 02_session_state.md
- Caching shared resources: 03_caching.md
- Auth & secrets: 06_deployment_and_secrets.md
Interview angle
- “How do you build a multipage app?” - a
pages/directory for file-based routing, orst.navigationwithst.Pagefor programmatic control, which is what you need to show pages conditionally based on role. - “Does state persist across pages?” -
st.session_statedoes; local variables don’t. Shared setup should live in a cached resource so navigating doesn’t re-initialise it. - “How do you handle access control?” - not with hidden pages alone, since file-based routes are reachable directly. Check authorisation at the top of each page, or use programmatic navigation to build the page list per user.