tensorflow
tf.shape
bug
debugging
tensor shapes

tf.shape get wrong shape in tensorflow

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

When tf.shape() seems to return the wrong result, the problem is usually not that TensorFlow computed the wrong shape. The real issue is usually a mismatch between static shape information, runtime shape information, or the developer's expectation of what an operation should have produced.

To debug shape issues in TensorFlow, you need to distinguish between tensor.shape and tf.shape(tensor), and you need to know when dimensions are known only at runtime.

Static Shape Versus Runtime Shape

TensorFlow exposes shape information in two different ways:

  • 'tensor.shape is the static shape known at graph-building time'
  • 'tf.shape(tensor) is an operation that returns the runtime shape'

These are often the same in eager mode for small examples, which is why the distinction is easy to miss.

python
1import tensorflow as tf
2
3x = tf.keras.Input(shape=(8,))
4print(x.shape)
5print(tf.shape(x))

x.shape will describe the symbolic shape, which is typically (None, 8) because the batch dimension is unknown. tf.shape(x) creates a runtime tensor representing the actual dimensions that will be known only when real data flows through the model.

If you expected 4, 8 because your batch size later happens to be 4, then the confusion comes from when the shape is being asked, not from TensorFlow returning the wrong answer.

Why None and Dynamic Dimensions Cause Confusion

Many TensorFlow pipelines intentionally leave some dimensions unknown. Batch size is the most common example, but sequence length can also be dynamic in NLP and time-series models.

Consider this function:

python
1import tensorflow as tf
2
3@tf.function
4def inspect_shape(x):
5    tf.print("static:", x.shape)
6    tf.print("runtime:", tf.shape(x))
7    return x
8
9
10tensor = tf.ones((3, 5))
11inspect_shape(tensor)

The static shape may be fully known in simple cases, but inside reusable traced functions or Keras models some dimensions stay symbolic. That is expected. tf.shape() is reporting the real runtime shape, while x.shape is reporting what TensorFlow could infer ahead of time.

Operations That Change Shape Indirectly

Another common source of confusion is forgetting that an earlier operation changed rank or dimension order. Reshaping, stacking, squeezing, expanding dimensions, batching datasets, and broadcasting all affect later shapes.

python
1import tensorflow as tf
2
3x = tf.constant([1, 2, 3, 4])
4print(tf.shape(x).numpy())  # [4]
5
6y = tf.expand_dims(x, axis=0)
7print(tf.shape(y).numpy())  # [1 4]
8
9z = tf.reshape(y, (2, 2))
10print(tf.shape(z).numpy())  # [2 2]

If you expected [4] all the way through, then the mistake is in the mental model of the transformations, not in tf.shape().

This becomes more subtle in input pipelines. A Dataset.batch(32) call inserts a leading batch dimension, and padded_batch() may change later dimensions as well.

Better Ways to Debug Shape Problems

A good workflow is:

  1. print both static and runtime shapes
  2. inspect the tensor immediately after each transformation
  3. add explicit assertions when shape contracts matter

TensorFlow includes assertion helpers for exactly this purpose:

python
1import tensorflow as tf
2
3def normalize_batch(x):
4    tf.debugging.assert_rank(x, 2)
5    tf.debugging.assert_shapes([(x, ("batch", "features"))])
6    return tf.math.l2_normalize(x, axis=1)
7
8
9data = tf.constant([[1.0, 2.0], [3.0, 4.0]])
10print(normalize_batch(data))

Assertions move the failure closer to the real source of the bug. Instead of discovering a mysterious downstream mismatch, you learn exactly where the tensor stopped matching the expected contract.

Common Pitfalls

  • Reading tensor.shape and tf.shape(tensor) as if they were interchangeable.
  • Assuming None means an error. In TensorFlow it often means "unknown until runtime."
  • Forgetting that batching, reshaping, squeezing, or broadcasting changed the tensor before you inspected it.
  • Debugging too late. Shape mismatches are much easier to understand if you inspect tensors right after each transformation.

Summary

  • 'tensor.shape is static metadata, while tf.shape(tensor) gives the runtime shape.'
  • Dynamic dimensions such as batch size often appear as None in static shape output.
  • Most "wrong shape" reports come from earlier operations that changed the tensor unexpectedly.
  • Print shapes close to the transformation that caused them.
  • Use tf.debugging.assert_rank and tf.debugging.assert_shapes to make tensor contracts explicit.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.