Streamlit Tutorial — Build Data Apps in Python in Minutes

Photo: RDNE Stock project / Pexels
Streamlit turns Python scripts into web apps without a line of HTML, CSS, or JavaScript. A data scientist who has never touched frontend development can go from a Jupyter notebook to a shareable browser URL in under an hour. That single fact explains why Snowflake paid roughly $800 million to acquire the company in March 2022 and why the open-source project has passed 40,000 GitHub stars.
This guide walks through installation, every core widget and chart type, a full working dashboard, caching, session state, ML model deployment, and every realistic deployment path — free, paid, and self-hosted. It also covers what Streamlit is bad at, because knowing the ceiling matters as much as knowing the floor.
Why Streamlit Matters for Data Scientists
The classic data scientist problem looks like this: you spend three weeks building a churn model or a demand forecast. The analysis is genuinely good. Then it sits in a .ipynb file that only you can run, because only you have the right Python environment, the right file paths, and the patience to scroll through forty cells to find the one that matters.
Nobody on the business side can touch it. The stakeholder who asked for the analysis has to email you every time they want a different date range or a different region filtered in. The model, however accurate, never actually gets used — it becomes a slide in a deck instead of a tool people return to.
Streamlit exists to close that gap. In roughly 30 lines of Python you get a shareable web app with file upload widgets, charts that redraw when a user moves a slider, form controls, and a public URL, all without hiring a frontend engineer or learning React. Snowflake's 2022 acquisition of Streamlit, reported at approximately $800 million by TechCrunch, was a bet that this "notebook to app" gap was worth owning at the platform level — Snowflake now ships Streamlit natively inside its data cloud so customers can build apps directly against warehoused data.
Think of Streamlit the way you'd think of a Polaroid camera next to a full darkroom setup. A darkroom (custom React frontend + API backend) gives you total control over exposure, cropping, and print quality, but it takes hours of setup and specialized skill. A Polaroid gives you a real, shareable photograph in under a minute, with the tradeoff that you can't fine-tune every variable. Most internal tools, prototypes, and stakeholder demos are Polaroid problems, not darkroom problems — and that's the market Streamlit owns.
What is Streamlit used for?
Streamlit is most commonly used to turn data analysis, ML model inference, and internal reporting into interactive web apps without frontend code. Typical use cases include ML model demos, internal business dashboards, data exploration tools for non-technical stakeholders, and rapid prototypes before a "real" product gets built.
Installation and Hello World
Getting a Streamlit app running takes one pip install and one file.
pip install streamlit
Create app.py:
import streamlit as st
st.title("Hello, Streamlit!")
st.write("My first data app.")
name = st.text_input("What's your name?")
if name:
st.success(f"Hello, {name}!")
Run it:
streamlit run app.py
A browser tab opens at localhost:8501 with your live app. Edit the file and save — Streamlit detects the change and offers to rerun automatically. There's no build step, no bundler, no webpack config to fight with.
That's the entire onboarding cost. Compare that to spinning up a minimal Flask + React app: you'd need a backend route, a frontend component, a fetch call wiring them together, and a dev server for each side before you've written a single line of actual logic. Streamlit collapses all of that into a script that reads top to bottom like a recipe.
Why does Streamlit rerun the whole script on every interaction?
Streamlit's execution model reruns your entire Python script from top to bottom every time a user interacts with a widget — clicking a button, moving a slider, typing in a box. This is intentional: it keeps the mental model simple (your script is always the current state of the app) at the cost of needing explicit caching for anything expensive.
Core Components
Display Text
st.title("Big Title")
st.header("Section Header")
st.subheader("Subsection")
st.write("Regular text — also handles dataframes, plots, anything")
st.markdown("**Bold** and *italic* and `code`")
st.caption("Small caption text")
st.write() is the Swiss Army knife of the library. Pass it a string, a Pandas DataFrame, a Matplotlib figure, a dictionary, or a NumPy array, and it renders sensibly by default. Most Streamlit tutorials teach you to reach for the specific function (st.dataframe, st.pyplot) once you need control over formatting, but st.write is where you start.
Input Widgets
# Text input
name = st.text_input("Your name", value="Default")
notes = st.text_area("Long text", height=150)
# Numbers
age = st.number_input("Age", min_value=0, max_value=120, value=30)
slider = st.slider("Pick a value", 0, 100, 50)
# Selections
color = st.selectbox("Favorite color", ["Red", "Green", "Blue"])
hobbies = st.multiselect("Hobbies", ["Reading", "Sports", "Coding"])
# Boolean
agree = st.checkbox("I agree")
clicked = st.button("Submit")
# File upload
file = st.file_uploader("Upload CSV", type=["csv"])
# Date/time
date = st.date_input("Pick a date")
time = st.time_input("Pick a time")
Every one of these widgets returns a plain Python value the instant the script runs — a string, a number, a list, a datetime object. There's no event listener boilerplate, no onChange handler, no separate state management library. You read the return value like you'd read a function's output, because that's exactly what it is.
Display Data
import pandas as pd
df = pd.read_csv("data.csv")
# Show dataframe (interactive — sortable, filterable)
st.dataframe(df)
# Static table
st.table(df.head())
# Metrics
st.metric("Revenue", "$12,345", delta="+10%")
# JSON
st.json({"key": "value"})
st.dataframe() is the one to reach for by default — it renders an interactive grid with column sorting and resizing baked in, backed by Glide Data Grid under the hood for performance on larger tables. st.table() renders a static, non-interactive HTML table, which is faster for very small, fixed datasets like a five-row summary.
Display Charts
import plotly.express as px
# Plotly (interactive)
fig = px.scatter(df, x="age", y="salary", color="department")
st.plotly_chart(fig)
# Matplotlib
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(df['date'], df['revenue'])
st.pyplot(fig)
# Built-in charts
st.line_chart(df.set_index('date')['revenue'])
st.bar_chart(df.groupby('department')['salary'].mean())
st.area_chart(df.set_index('date'))
The built-in chart functions (st.line_chart, st.bar_chart, st.area_chart) are wrappers around Vega-Lite and are the fastest path to something reasonable-looking. For anything that needs hover tooltips, zoom, or multi-series legends with fine control, st.plotly_chart is the workhorse — it's what almost every production Streamlit dashboard uses once the prototype phase is over.
Complete Example: Sales Dashboard
Here's a full, working dashboard that pulls together filters, metrics, charts, and a raw data view — the shape of roughly 80% of the internal tools built with Streamlit in the wild.
import streamlit as st
import pandas as pd
import plotly.express as px
st.set_page_config(
page_title="Sales Dashboard",
page_icon="📊",
layout="wide"
)
# Load data (with caching)
@st.cache_data
def load_data():
return pd.read_csv("sales.csv", parse_dates=['date'])
df = load_data()
# Sidebar filters
st.sidebar.header("Filters")
date_range = st.sidebar.date_input(
"Date range",
[df['date'].min(), df['date'].max()]
)
departments = st.sidebar.multiselect(
"Departments",
df['department'].unique(),
default=df['department'].unique()
)
# Filter data
filtered = df[
(df['date'] >= pd.Timestamp(date_range[0])) &
(df['date'] <= pd.Timestamp(date_range[1])) &
(df['department'].isin(departments))
]
# Main dashboard
st.title("📊 Sales Dashboard")
# Top metrics row
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Revenue", f"${filtered['revenue'].sum():,.0f}")
col2.metric("Orders", f"{len(filtered):,}")
col3.metric("Avg Order", f"${filtered['revenue'].mean():.0f}")
col4.metric("Unique Customers", f"{filtered['customer_id'].nunique():,}")
# Charts
st.header("Revenue Over Time")
revenue_by_date = filtered.groupby('date')['revenue'].sum().reset_index()
fig = px.line(revenue_by_date, x='date', y='revenue')
st.plotly_chart(fig, use_container_width=True)
col_left, col_right = st.columns(2)
with col_left:
st.subheader("Revenue by Department")
dept_revenue = filtered.groupby('department')['revenue'].sum().reset_index()
fig = px.bar(dept_revenue, x='department', y='revenue')
st.plotly_chart(fig, use_container_width=True)
with col_right:
st.subheader("Top Products")
top_products = filtered.groupby('product')['revenue'].sum().nlargest(10).reset_index()
st.dataframe(top_products, use_container_width=True)
# Raw data viewer
with st.expander("View raw data"):
st.dataframe(filtered)
This is a fully working dashboard in roughly 60 lines. Building the equivalent in a JavaScript stack — a Flask or FastAPI backend serving JSON, a React frontend consuming it, a charting library wired up, state management for the filters — realistically takes two to three days for a developer who knows the stack well, longer for a data scientist learning it from scratch. Streamlit compresses that into an afternoon.
Caching for Performance
Because Streamlit reruns the entire script on every interaction, caching isn't optional polish — it's the difference between an app that feels instant and one that reloads a multi-gigabyt CSV every time someone clicks a checkbox.
@st.cache_data
def load_large_csv():
return pd.read_csv("huge_file.csv") # Loads once, cached thereafter
@st.cache_resource
def load_ml_model():
return joblib.load("model.pkl") # Cache shared across users
@st.cache_data(ttl=3600)
def fetch_api_data():
return requests.get("https://api.example.com").json() # Refresh hourly
@st.cache_data is for anything that returns serializable data — DataFrames, dicts, lists, arrays. Streamlit hashes the function's inputs and, if it's seen that exact call before, returns the cached result instead of re-executing. @st.cache_resource is for objects that shouldn't be copied per session, like a loaded ML model, a database connection, or a large embedding index — it caches a single shared instance across every user hitting the app.
Getting this distinction wrong is one of the most common beginner mistakes: caching a database connection with @st.cache_data will try to serialize and hash a connection object, which either fails outright or silently creates a new connection every run, defeating the purpose.
How does caching actually make Streamlit apps faster?
Streamlit caching stores the output of a decorated function keyed by a hash of its inputs. On the next call with the same inputs, Streamlit skips re-running the function entirely and returns the stored result in milliseconds — turning a five-second CSV load or model inference into an instant return on every rerun after the first.
Session State
Because the script reruns from scratch on every interaction, Streamlit needs a dedicated mechanism to remember values across reruns — that's st.session_state.
# Initialize
if 'count' not in st.session_state:
st.session_state.count = 0
# Update on button click
if st.button("Increment"):
st.session_state.count += 1
st.write(f"Count: {st.session_state.count}")
Session state behaves like a Python dictionary that persists for the duration of a user's browser session. It's essential for multi-step forms (where step 2 needs to remember what was entered in step 1), accumulating a shopping-cart-style list of selections, or tracking whether a user has already dismissed a warning. Without it, every rerun would wipe local variables back to their initial values, and nothing would ever feel "remembered."
One frequent bug: assigning to a variable inside the script body (count = 0) does not persist across reruns the way st.session_state.count does, because plain local variables are recreated from scratch on every single script execution.
ML Model Deployment
The single most common production pattern for Streamlit is wrapping a trained model in a prediction form so non-technical stakeholders can query it without touching Python.
import streamlit as st
import joblib
import pandas as pd
@st.cache_resource
def load_model():
return joblib.load("churn_model.pkl")
model = load_model()
st.title("Customer Churn Predictor")
# Input form
col1, col2 = st.columns(2)
with col1:
tenure = st.number_input("Months as customer", 0, 100)
monthly_charges = st.number_input("Monthly charges ($)", 0.0, 500.0)
with col2:
contract = st.selectbox("Contract", ["Monthly", "1 Year", "2 Year"])
payment = st.selectbox("Payment", ["Auto", "Credit Card", "Mailed Check"])
if st.button("Predict"):
input_df = pd.DataFrame([{
'tenure': tenure,
'monthly_charges': monthly_charges,
'contract': contract,
'payment_method': payment
}])
probability = model.predict_proba(input_df)[0][1]
st.metric("Churn Probability", f"{probability:.1%}")
if probability > 0.7:
st.error("High risk of churn — recommend intervention")
elif probability > 0.4:
st.warning("Moderate risk")
else:
st.success("Low risk")
Stakeholders can now use the ML model directly, no notebook and no API contract required. This pattern shows up constantly in real deployments: retail teams building churn or demand-forecast tools, healthcare researchers wrapping risk-scoring models for clinical review (never for direct patient-facing diagnosis — that's a regulatory and safety line Streamlit apps should never cross without formal clinical validation), and finance teams building loan-risk or fraud-scoring interfaces for analysts.
It's worth being explicit here: an app like the one above is an internal decision-support tool, not a substitute for a validated, monitored production ML pipeline. For anything touching real customer decisions at scale — pricing, credit, medical risk — the model behind the Streamlit form should go through the same rigor (test sets, drift monitoring, audit trail) that any production model needs, covered in more depth in our MLOps for beginners guide.
Deployment
Streamlit Community Cloud (Free)
- Push code to GitHub.
- Sign up at share.streamlit.io.
- Connect the GitHub repo.
- Deploy — a public URL is live within roughly 60 seconds for small apps.
Community Cloud is free for public apps and is the default choice for portfolios, prototypes, and internal proof-of-concepts you want to share with a small team quickly. The tradeoff: apps go to sleep after a period of inactivity and wake with a short cold-start delay, and compute resources are shared and capped.
Streamlit in Snowflake / Streamlit Cloud Teams
Since the 2022 acquisition, Snowflake has integrated Streamlit natively into its platform (Streamlit in Snowflake), letting teams build apps that query warehouse data directly without moving it. Separately, Streamlit Community Cloud offers paid tiers for private apps, custom domains, and team workspace management, aimed at companies that need SSO and access control rather than a public demo link.
Self-Hosted
# Docker
docker run -p 8501:8501 streamlit:latest streamlit run app.py
# Run on any VPS, AWS, GCP, Azure
Self-hosting via Docker on a VPS, AWS EC2, GCP Cloud Run, or Azure App Service gives full control over compute, networking, and data residency, at the cost of recurring infrastructure spend and someone owning the operational burden — patching, scaling, uptime monitoring. For a single internal tool used by a dozen people, this is often overkill; for a customer-facing app with real traffic, it's often necessary.
Streamlit vs Alternatives
Choosing between Streamlit, Dash, Gradio, Panel, and Shiny for Python comes down to who's building the app and what it needs to do once it's built.
| Tool | Best For | Rerun Model | Difficulty |
|---|---|---|---|
| Streamlit | Data scientists, quick prototypes, internal dashboards | Full script rerun per interaction | Easy |
| Plotly Dash | Complex, multi-page enterprise apps needing fine control | Callback-based — only affected components rerun | Medium |
| Gradio | ML model demos, especially on Hugging Face Spaces | Full function rerun per interaction | Easy |
| Panel | Holoviz ecosystem (Bokeh, hvPlot) integration | Callback-based | Medium |
| Shiny for Python | R users transitioning to Python, reactive-programming fans | Reactive graph, fine-grained | Medium |
Comparison of Python data app frameworks by primary use case, execution model, and learning curve.
Streamlit wins on raw speed to a working prototype for roughly 80% of data science use cases: dashboards, model demos, internal reporting tools. Dash's callback architecture means only the components affected by an interaction actually re-execute, which gives it a real performance edge for large, complex, multi-page apps — the cost is a steeper learning curve and noticeably more boilerplate per interactive element. Gradio is purpose-built for ML model demos and has first-class Hugging Face Spaces hosting, making it the default choice specifically when the entire point of the app is "type a prompt or upload an image, see the model's output."
Is Streamlit better than Dash?
Neither is universally better — they optimize for different problems. Streamlit is faster to build with and has a friendlier API, which is why teams default to it for most dashboards. Dash is more customizable and generally performs better at scale because it only reruns the specific callback affected by an interaction rather than the entire script, which matters once an app has heavy computation or very large datasets.
Common Pitfalls
1. Forgetting to cache expensive operations. Every interaction reruns the whole script — without @st.cache_data or @st.cache_resource, a five-second data load happens on every single click.
2. Not using use_container_width=True. Plotly and dataframe elements default to a fixed width that can look cramped inside Streamlit's column layout; this parameter lets them fill the available space.
3. Mixing layout with logic. Reaching for st.columns() and st.tabs() early keeps the script readable as the app grows past a single-page prototype.
4. No error handling. File uploads, external API calls, and model predictions all fail in the real world — wrapping them in try/except with an st.error() message prevents a raw Python traceback from reaching an end user.
5. Hardcoded paths. A path like "C:/Users/name/data.csv" works on one machine and breaks the moment the app is deployed anywhere else — use relative paths, st.secrets for credentials, or environment variables.
6. Treating Streamlit as a production API. Each connected user reruns the full script server-side. That's fine for a dozen concurrent internal users; it is not a substitute for a proper backend API serving thousands of requests per second. For high-traffic, latency-sensitive workloads, the model belongs behind a dedicated inference API with Streamlit (or nothing at all) as a thin client on top.
7. Skipping input validation on ML forms. A churn predictor that accepts a negative "months as customer" value without pushback will happily return a confident, meaningless prediction. Setting min_value/max_value on numeric widgets and validating selectbox combinations before calling .predict() catches this early.
Where Streamlit Falls Short
It's worth being direct about the ceiling. Streamlit's rerun-the-whole-script model, which makes it so easy to learn, is also its biggest architectural limitation. Every widget interaction reruns everything above and below it unless you've explicitly cached the expensive parts, which means apps with heavy computation or multiple independent sections can feel sluggish under real usage in a way a callback-based framework like Dash typically doesn't.
Pixel-level design control is also limited by design. Streamlit intentionally trades customization for speed — there's no equivalent of hand-crafting a CSS grid or a bespoke component library the way you would in React. Teams that need a polished, brand-matched customer-facing product usually outgrow Streamlit and rebuild the frontend once the prototype has proven the underlying idea is worth investing in properly.
Concurrency is the other honest limitation. Streamlit Community Cloud's free tier and even the paid tiers are built around light-to-moderate simultaneous usage — a few dozen people hitting an internal tool at once, not thousands of anonymous visitors hammering a public endpoint. Scaling past that point means self-hosting with a real load balancer in front of multiple app instances, at which point you're doing real infrastructure engineering regardless of which app framework you started with.
FAQ
Q: Can Streamlit handle production traffic? A: Light to moderate traffic only. Each connected user reruns the script server-side, so heavy compute or thousands of concurrent users scales poorly without significant self-hosted infrastructure work. For genuine high-traffic production, build a proper API and a dedicated frontend instead.
Q: Can users save their work in a Streamlit app?
A: Yes, through st.session_state for the duration of a browsing session, through file download buttons (st.download_button), or by wiring the app to a backend database for persistence across sessions.
Q: Can I embed a Streamlit app inside another website?
A: Yes, via an <iframe>. Rendering quality and load time vary depending on how the app is hosted — Community Cloud apps can have a cold-start delay after inactivity that's noticeable inside an embed.
Q: How does Streamlit compare to a hand-built React app? A: Streamlit is dramatically faster to build and requires no frontend skill, but gives up fine-grained design control and raw runtime performance. Use Streamlit for internal tools and demos that don't need pixel-perfect, brand-matched design; use React (or a framework like Next.js) when the frontend itself is the product.
Q: Does Streamlit require any JavaScript knowledge? A: No. The entire app, including layout, widgets, and charts, is written in Python. JavaScript only becomes relevant if you build a custom Streamlit component, which is an advanced, optional path most users never need.
Q: How much does Streamlit cost to run? A: The open-source library is free and MIT-licensed. Streamlit Community Cloud is free for public apps. Private apps, custom domains, and team features require a paid plan; self-hosting on a VPS or cloud provider costs whatever the underlying compute costs, typically a few dollars a month for a small internal tool.
Q: Is Streamlit good for machine learning model demos specifically? A: It works well, but Gradio is generally the sharper choice for pure ML model demos, particularly image, audio, or chat-style interfaces, because of its tighter integration with Hugging Face Spaces hosting and its interface primitives built specifically around model inputs and outputs.
Q: Who owns Streamlit now? A: Streamlit is owned by Snowflake, which acquired the company for approximately $800 million in March 2022, according to TechCrunch. The open-source library remains free and independently usable outside of Snowflake's platform.
Q: What's the best way to learn Streamlit? A: Streamlit's own documentation at docs.streamlit.io is thorough and includes interactive, runnable examples for nearly every function in the API, and is the most reliably up-to-date resource since the library ships frequent releases.
Q: Can Streamlit connect to a live database instead of a static CSV?
A: Yes. Streamlit has built-in connection helpers (st.connection) for SQL databases, and the standard pattern is to wrap the query function in @st.cache_data(ttl=...) so the app queries fresh data on a schedule rather than on every single user interaction.
Related Articles
- Data Visualization Tools 2026
- Best Data Science Tools 2026
- Build Data Science Portfolio
- MLOps Explained for Beginners
The Takeaway
Streamlit's real innovation isn't any single widget or chart type — it's the decision to make "rerun the whole script" the entire mental model, trading raw performance and design control for a learning curve so shallow that a data scientist with zero frontend experience can ship a real, usable web app in an afternoon. That tradeoff is exactly right for internal tools, ML demos, and prototypes, and exactly wrong for high-traffic, pixel-perfect, customer-facing products. Know which category your app falls into before you start, and Streamlit will save you days; guess wrong, and you'll rebuild the frontend later anyway.
Last verified: July 2026
Follow new articles
Add SciHub101 to your feed reader to see new science and technology stories.
About the Author
SciHub101 is an independent science and technology publication founded and edited by Buğra Sözeri, a software engineer and app developer based in Türkiye. AI tools assist with drafting and research. Source documentation in the existing archive is uneven, and articles are being reviewed and corrected. The Editorial Team byline identifies editorial responsibility; it does not certify specialist or independent human review. Where we compare products, anything built by our own team is labelled as such — see the editorial guidelines and About page for how corrections and disclosures work.
Related Articles

Statistics for Data Scientists 2026 — What You Actually Need
Statistics working data scientists use: descriptive summaries, distributions, hypothesis tests, regression, causal inference, plus worked A/B numbers.

DuckDB vs Pandas — Which to Use for Data Analysis 2026
DuckDB vs Pandas: when to use each, performance benchmarks, SQL vs Python syntax, real examples. DuckDB is faster and handles larger data — should you switch?

Python for Data Science: Complete Guide 2026
Master Python for data science in 2026. Covers essential libraries, workflows, career paths, and learning roadmap with real examples.

NumPy Guide — Python's Foundation for Data Science
Learn NumPy from scratch — arrays, broadcasting, vectorization, indexing, and linear algebra. The bedrock library behind pandas, scikit-learn, and PyTorch.

Pandas Python Guide — Data Analysis for Beginners
Learn pandas: DataFrames, loading, cleaning, groupby, merging, and plotting, with pandas 3.0 code examples and a full CSV-to-insight analysis pipeline.

Python Data Libraries Cheat Sheet 2026 — Quick Reference
Quick reference for Python data libraries 2026: Pandas, Polars, DuckDB, NumPy, Plotly. Common operations side-by-side. Bookmark for daily work.