Skip to content
Everyone knows 3 min read · Strings 1 practice problem ↓

String functions

concat vs concat_ws, substring, split, trim and padding, with the null traps in each.

You will learn

  • The difference between concat and concat_ws, and the null trap
  • How to cut strings with substring and split
  • How to clean text with trim, lower, upper, initcap and lpad
  • Which string functions are 1-based and which are 0-based

Read first

Comfortable with these? Read on.

TL;DR Spark has a built-in function for almost every string job, and built-ins are much faster than Python UDFs. Remember three rules: concat returns null if any input is null, while concat_ws skips nulls; substring starts counting at 1; and split takes a regular expression.

concat vs concat_ws

FunctionWith a null inputExample
concat(a, b, ...)The whole result is nullconcat("Bo", " ", null) → null
concat_ws(sep, a, b, ...)The null is skippedconcat_ws(" ", "Bo", null) → "Bo"

For names, addresses and keys built from several columns, concat_ws is almost always what you want. If you build a join key with concat, rows with any null part silently stop matching.

Run the example

PySparkSpark SQL
from pyspark.sql import functions as F
result = customers.select(
    "id",
    F.concat("first", F.lit(" "), "last").alias("with_concat"),
    F.concat_ws(" ", F.initcap(F.trim("first")), "last").alias("full_name"),
    F.upper(F.substring("city", 1, 3)).alias("city_code"),
    F.split("phone", "-").alias("phone_parts"),
)
SELECT id,
       concat(first, ' ', last)                    AS with_concat,
       concat_ws(' ', initcap(trim(first)), last) AS full_name,
       upper(substring(city, 1, 3))               AS city_code,
       split(phone, '-')                          AS phone_parts
FROM customers

Switch to PySpark to edit and run this example in your browser.

Row 2 shows the null trap: with_concat is null because last is null, while full_name is "Bo". Row 1 shows why cleaning comes first: the raw value " ana " keeps its spaces inside with_concat.

The toolkit

JobFunctionNotes
Remove spacestrim, ltrim, rtrimRemoves spaces only; tabs and non-breaking spaces need regexp_replace(c, r"^\s+|\s+$", "")
Change caselower, upper, initcapLower-case both sides before comparing or joining on text
Cutsubstring(c, pos, len)1-based. A negative pos counts from the end: substring(c, -4, 4) is the last 4 characters
Splitsplit(c, pattern)The pattern is a regex: split on a dot with "\\.", not "."
Pick a piecesplit(c, "-")[0] or F.split(c, "-").getItem(0)0-based, unlike substring
LengthlengthCharacters, not bytes
Padlpad(c, 5, "0"), rpadZero-pad codes; also truncates values longer than the width
Replacereplace (literal, Spark 3.5+), regexp_replace, translatetranslate(c, "-.", "") deletes characters one by one
Findinstr, locate, contains, startswith, likeinstr and locate return 1-based positions, 0 if not found

1-based or 0-based?

SQL functions count from 1: substring, instr, locate, element_at. Array indexing with brackets or getItem counts from 0. split("98-765-43", "-")[0] is "98", while element_at(split(...), 1) is also "98".

Cleaning text before a join

Text keys from two systems rarely match exactly. Normalise both sides the same way before joining: F.lower(F.trim(c)), and remove punctuation with regexp_replace when needed. A join on raw names silently drops "Pune" vs "pune " pairs.

Why this matters: every built-in function runs inside the JVM on whole batches of rows, and CatalystCatalyst: Spark's query optimizer. It rewrites your DataFrame code into a faster equivalent plan before running it. Learn more → can optimise around it. A Python UDFUDF: User-defined function: your own Python function applied to each row. Flexible but much slower than built-in functions. Learn more → that does the same .strip().lower() ships every row to a Python process and back, typically several times slower.

Common mistakes

Building keys with concat

Any null part makes the whole key null. Use concat_ws, or coalesce each part first.

split on "." or "|"

Both are regex metacharacters. Escape them: "\\." and "\\|".

Off-by-one between substring and split

substring is 1-based, array indexing is 0-based.

Joining on uncleaned text

Trailing spaces and case differences make rows fail to match. Trim and lower both sides.

Key takeaways

  • concat returns null if any part is null; concat_ws skips nulls.
  • substring is 1-based; array indexing is 0-based.
  • split takes a regex, so escape dots and pipes.
  • Normalise text with trim and lower before comparing or joining.

Check yourself

3 questions

1. What is concat("a", null, "b")?

Show the answer

null. concat returns null when any input is null.

2. What does substring("Mumbai", 1, 3) return?

Show the answer

"Mum". substring is 1-based: start at the first character and take 3.

3. Why does split("a.b.c", ".") not split on dots?

Show the answer

The pattern is a regex and "." matches any character. Escape it as "\\." to split on a literal dot.

Practice it

Interview problems that use this: write the PySpark, run it, and get graded on hidden tests.

Solve: Word Count →

Keep going

Up next · lesson 7 of 35 · 3 min read
Parsing dates and timestamps
to_date, to_timestamp and date_format patterns, and why yyyy and YYYY are not the same.

Related lessons

Previous: cast and schemas

Primary sources: String functions · functions.concat_ws