Tensor Flow 📂 Tensors · 1 of 6 34 min read

What Is TensorFlow? Eager Mode and tf.function Explained

Learn what TensorFlow is and how tensors flow through operations. See what happens when one line of TF code runs. Compare eager mode and graph mode in a live simulator. Then learn how tf.function traces your code into a fast graph, and why passing Python numbers causes slow retracing.

Section 01

The Story That Explains TensorFlow

The Home Cook and the Restaurant Kitchen
A home cook works one step at a time. Chop the onion. Taste. Add salt. Taste again. Every step gives a result right away. If something goes wrong, you see it at once.

A restaurant kitchen works differently. The chef first writes the full recipe as a plan. Then the whole team runs that plan again and again, very fast, for hundreds of plates.

TensorFlow can work in both ways. The home-cook style is called eager mode. The restaurant style is called graph mode. You start with eager mode. You switch to graph mode when you need speed.

TensorFlow is a free, open-source library from Google for maths on large arrays of numbers. Its main use is machine learning and deep learning. It can run the same code on a CPU, a GPU, or a TPU. It can also compute gradients for you automatically. That is the key thing a neural network needs to learn.

💡
The Name Says It All

A tensor is a box of numbers with a fixed shape, like a list, a table, or a cube. Flow means these tensors move through a chain of operations. So TensorFlow = tensors flowing through maths operations.


Section 02

Three Building Blocks

🧱
Tensors
the data
Multi-dimensional arrays. Every tensor has a shape and a dtype. Images, text and audio all become tensors.
⚙️
Operations (Ops)
the work
Functions that take tensors in and give tensors out. For example tf.add, tf.matmul, tf.nn.relu.
🖥️
Devices
the worker
Where an op runs: CPU:0, GPU:0 or a TPU. TensorFlow picks the best device for you by default.
Animated Diagram — Tensors Flowing Through Ops
x(32, 784) W(784, 10) matmul(32, 10) b (10,) add(32, 10) relu(32, 10) y

This is one dense layer: y = relu(x @ W + b). A batch of 32 images, each with 784 pixels, becomes 32 rows of 10 scores.


Section 03

The TensorFlow Family

TensorFlow is not one tool. It is a family of tools that work together. You will meet most of them in this course.

🧠
tf.keras
The high-level API to build and train neural networks in a few lines.
layers, models, fit()
📦
tf.data
Fast input pipelines. It loads, shuffles and batches data while the model trains.
Dataset.map / batch
📈
TensorBoard
A dashboard to watch loss curves, graphs and weights while training.
%tensorboard --logdir
📱
LiteRT (TF Lite)
Runs small models on phones and edge devices.
.tflite files
🌐
TensorFlow.js
Runs models inside a web browser or Node.js.
tfjs
🚀
TF Serving
Serves a trained model as a REST or gRPC API in production.
SavedModel

Section 04

What Happens When You Run One Line

Take this line: tf.add(a, b). Here is what TensorFlow does behind the scenes.

01
Python Call
Your Python code calls tf.add. Python itself does no maths here.
02
Op Dispatch
TensorFlow finds the AddV2 op. It checks that the shapes and dtypes of a and b fit.
03
Device Placement
If a GPU is present, TensorFlow places the op on GPU:0. Otherwise it uses CPU:0.
04
Kernel Runs in C++ / CUDA
A fast, compiled kernel does the real work. This is why TensorFlow is much faster than a Python loop.
05
Result Returned
In eager mode you get back an EagerTensor with real values at once.

Section 05

Eager Mode — The Default

Since TensorFlow 2, eager execution is on by default. Every op runs the moment you call it. You get real numbers back. It feels just like NumPy.

import tensorflow as tf

print("TensorFlow version:", tf.__version__)
print("Eager mode on?    ", tf.executing_eagerly())

a = tf.constant([[1, 2], [3, 4]])
b = tf.constant([[10, 20], [30, 40]])

c = a + b              # runs right now
print(c)
print("As NumPy:", c.numpy().tolist())
OUTPUT
TensorFlow version: 2.21.0 Eager mode on? True tf.Tensor( [[11 22] [33 44]], shape=(2, 2), dtype=int32) As NumPy: [[11, 22], [33, 44]]

Because ops run at once, normal Python works fine. You can use if, for and print anywhere. That makes debugging easy.

x = tf.constant([3.0, -1.0, 4.0, -2.0])

for value in x:                    # plain Python loop
    if value > 0:
        print(float(value), "is positive")
    else:
        print(float(value), "is negative -> relu gives", float(tf.nn.relu(value)))
OUTPUT
3.0 is positive -1.0 is negative -> relu gives 0.0 4.0 is positive -2.0 is negative -> relu gives 0.0
✅
Why Eager Mode Is Great for Learning

You see every value. You can set a breakpoint. Errors point to the exact line. Use eager mode while you learn, test and debug.


Section 06

Try It — Eager vs Graph Simulator

Enter your own values. Then run the same small program in both modes. Watch how and when the values appear.

🎮 Eager vs Graph Execution Interactive

Program: y = relu(x * w + b). Eager mode runs each op one by one. Graph mode first builds the plan (no values yet), then runs it all in one go.

x = 3 w = -2 b = 4 multiply— add— relu— y
# Press a button to start
👀
What to Notice

In eager mode, each box gets a value right after its step. In graph mode, all boxes show ? during tracing. They are symbolic tensors with a shape but no value. Values arrive only when the finished graph runs.


Section 07

Graph Mode with tf.function

Add the @tf.function decorator to a Python function. TensorFlow then turns it into a graph. A graph is a saved plan of all the ops. TensorFlow can optimise this plan, run parts in parallel, and export it to phones or servers.

@tf.function
def dense_step(x, w, b):
    print("Python print -> runs only while TRACING")
    tf.print("tf.print     -> runs on EVERY call")
    return tf.nn.relu(x * w + b)

x = tf.constant(3.0); w = tf.constant(-2.0); b = tf.constant(4.0)

print("Call 1:"); dense_step(x, w, b)
print("Call 2:"); dense_step(x, w, b)
print("Call 3 (new value, same type):")
print("Result:", dense_step(tf.constant(1.0), w, b).numpy())
OUTPUT
Call 1: Python print -> runs only while TRACING tf.print -> runs on EVERY call Call 2: tf.print -> runs on EVERY call Call 3 (new value, same type): tf.print -> runs on EVERY call Result: 2.0

Look closely. The first call printed both lines. Calls 2 and 3 printed only the tf.print line. On the first call, TensorFlow ran your Python code once to trace it. After that, it reuses the graph and skips the Python code.

⚠️
Note: tf.print Goes to a Different Stream

tf.print writes to standard error, not standard output. In Colab and Jupyter you will see its lines mixed in with normal output. Use tf.print inside a tf.function when you need to see values on every call.

When Does TensorFlow Trace Again?

A graph is tied to an input signature. For tensors, the signature is the shape and dtype. For plain Python numbers, the signature is the exact value. A new signature means a new trace.

@tf.function
def square(v):
    print("  tracing for", v)
    return v * v

print("Tensors:")
square(tf.constant(2)); square(tf.constant(5))       # same shape+dtype -> 1 trace
square(tf.constant(2.0))                              # new dtype -> trace again
print("Python ints:")
square(2); square(5); square(2)                       # every new value -> trace
OUTPUT
Tensors: tracing for Tensor("v:0", shape=(), dtype=int32) tracing for Tensor("v:0", shape=(), dtype=float32) Python ints: tracing for 2 tracing for 5

Section 08

Try It — The Tracing Lab

🔍 Will This Call Trace a New Graph? Interactive

Type an argument for square(v) and press Call. Try tf.constant(3), tf.constant(7), tf.constant(3.5), tf.constant([1, 2]), 4, 9.

# Graph cache is empty
🚧
Retracing Is Slow

Each trace costs time. If you pass Python numbers in a training loop, TensorFlow traces on every new value. Your "fast" graph becomes slower than eager mode. Always pass tensors into a tf.function.


Section 09

Eager vs Graph — Side by Side

FeatureEager ModeGraph Mode (tf.function)
When ops runRight away, line by lineAfter the graph is traced
DebuggingEasy — print, breakpointsHarder — use tf.print
SpeedGoodFaster for many small ops
Python control flowWorks as normalConverted to graph ops by AutoGraph
Export (SavedModel, LiteRT)NoYes
Best forLearning, testing, debuggingTraining loops, production
🧠
Good News for Keras Users

When you call model.fit(), Keras wraps the training step in a tf.function for you. So you get graph speed without writing any decorator.


Section 10

Golden Rules

🎯 TensorFlow Basics — Rules to Remember
1
TensorFlow 2 runs in eager mode by default. Ops give real values at once.
2
Write and debug your code in eager mode first. Add @tf.function only when it works.
3
Inside a tf.function, Python print runs only while tracing. Use tf.print to see values on every call.
4
Pass tensors, not Python numbers, into a tf.function. This avoids slow retracing.
5
Use .numpy() to turn an eager tensor into a NumPy array for printing or plotting.