Groovy (Jenkins, Gradle and Spock) Module 2
Core syntax: values, strings, operators and control flow interview questions
Interview questions on “Core syntax: values, strings, operators and control flow”: warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher to mid
Why is 1 / 2 equal to 0.5 in Groovy, and how do you get Java's integer division?
-
Fresher to mid
What is the difference between 'Hi ${name}' and "Hi ${name}" in Groovy?
Answered in the lesson Strings, GStrings and multi-line text
-
Fresher to mid
What does the Elvis operator do in Groovy, and when is it the wrong choice?
Answered in the lesson Groovy truth, null safety and the Elvis operator
-
Fresher to mid
What does the spread operator *. do in Groovy, and what does it give for a null list?
Answered in the lesson Operators, ranges, spread and membership
-
Fresher to mid
How does Groovy's switch decide that a case matches?
Answered in the lesson Control flow, loops and the Groovy switch
-
Fresher to mid
What is the difference between =~ and ==~ in Groovy?
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
A Groovy job reads invoice amounts such as 19.99 from a CSV, converts each with toDouble() and stores whole cents with (amount * 100) as long. Some invoices come out one cent short. Explain the cause, fix the conversion, and say how you would test the fix.
- What it tests
- Binary floating point against BigDecimal, truncating conversions, exact conversion with longValueExact(), and test values that expose the loss.
- First step
- Reproduce it with one value. As a double, 19.99 times 100 is 1998.9999999999998, and as long cuts the fraction off, giving 1998. The double never held 19.99 exactly, so the fix starts where the text is parsed: read it with toBigDecimal() instead of toDouble().
- Follow-ups
-
- What should the job do with an amount such as 19.995?
- Would Math.round() be an acceptable fix?
- How do you keep the invoice total exact when the lines are added up?
From the lesson Numbers, variables, def, var and val
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A shared library rounds build durations with seconds.round(), where seconds is a BigDecimal. Its tests pass on Groovy 5.1.3, but in Jenkins the same call fails with NullPointerException: Cannot read field "precision". Explain why, and give a fix that behaves the same in both places.
- What it tests
- Knowing which Groovy each environment runs, that GDK methods appear in particular versions, and preferring Java API calls whose behaviour does not change.
- First step
- Ask which Groovy runs in each place: the tests use 5.1.3, while Jenkins 2.580.1 bundles groovy-all 2.4.21. The GDK lists BigDecimal.round() only since Groovy 2.5.0, so on 2.4.21 the call reaches Java's own round(MathContext) without a MathContext, and that method fails on the missing argument.
- Follow-ups
-
- Which rounding rule does Groovy's round() follow, and how do you write it explicitly?
- How would you have caught this before the library reached Jenkins?
- Which other calls are risky in code that runs on Groovy 2.4?
From the lesson Numbers, variables, def, var and val
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A deployment script builds a map of image tags with keys written "${service}-${env}". Printing the map shows api-prod, but tags['api-prod'] returns null. Explain the cause, give two fixes, and say how a test would have caught it.
- What it tests
- The difference between Groovy's == and Java's equals and hashCode, GString keys in collections, and writing a test that looks keys up the way callers do.
- First step
- Print the class of a key: tags.keySet()[0].getClass() shows GStringImpl, not String. A hash map finds keys by hashCode and equals, and a GString and a String with the same text differ in both, so the String lookup misses even though the printed map looks right.
- Follow-ups
-
- Why does tags["${service}-${env}"] miss as well?
- Which fix would you choose for a map that other teams read?
- Does the same problem affect a list or a set?
From the lesson Strings, GStrings and multi-line text
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Reading a colleague's Jenkinsfile, you see sh "deploy --token ${env.DEPLOY_TOKEN} --target ${params.TARGET}". What two problems does it have, how do you rewrite it, and how would you show that the rewrite fixes them?
- What it tests
- Knowing that Pipeline interpolates like Groovy, the exposure of secrets in command text, injection through parameters, and quoting for the shell.
- First step
- Notice the double quotes: the argument is a GString, so Groovy puts the token and the target into the command text before the shell starts. That exposes the token in the process arguments, and any ; or $ in TARGET becomes shell syntax. The fix keeps both values out of the text.
- Follow-ups
-
- Where should the token come from instead?
- Why is sh "deploy --target '${params.TARGET}'" not enough?
- How would you find other steps like this across many Jenkinsfiles?
From the lesson Strings, GStrings and multi-line text
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A pipeline step runs if (env.SKIP_TESTS) { echo 'Skipping tests' } else { runTests() }. A job sets SKIP_TESTS to the text false, and the tests are still skipped. Explain why, fix the check, and say how you would make every flag of the shared library behave the same way.
- What it tests
- Groovy truth for text, converting flags with toBoolean(), the difference between a missing and a false flag, and putting one rule in one helper.
- First step
- An environment value is always text, and text is true whenever it is not empty. 'false' has five characters, so the condition is true and the tests are skipped. The fix converts the text before it is tested, for example with env.SKIP_TESTS?.toBoolean().
- Follow-ups
-
- What does toBoolean() return for 'yes', 'TRUE' and ' 1 '?
- How should a missing SKIP_TESTS behave, and how do you make that explicit?
- How would you test the flag helper on the Groovy that Jenkins runs?
From the lesson Groovy truth, null safety and the Elvis operator
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A deploy helper reads int retries = config.retries ?: 3. A team sets retries to 0 to stop retrying a step that is not safe to repeat, and the step still runs three times. Explain the cause, write a fix that also compiles in a Jenkinsfile, and say how you would test it.
- What it tests
- The Elvis operator and Groovy truth, null checks for defaults, which syntax Groovy 2.4.21 accepts, and choosing test cases from the values that truth treats specially.
- First step
- The Elvis operator uses Groovy truth, and 0 is false, so config.retries ?: 3 gives 3 for a deliberate 0 exactly as for a missing value. The default has to be chosen by comparing with null instead.
- Follow-ups
-
- Why not write retries ?= 3?
- Which other settings in such a helper have the same problem?
- How do you tell a missing setting from one set to null on purpose?
From the lesson Groovy truth, null safety and the Elvis operator
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A release script treats a feature as on with if ('dryRun' in options), where options = [dryRun: false] means off. It behaved on Groovy 5.1.3, but after the move to 6.0.0 every release runs as a dry run. Explain the change, write a check that means the same everywhere, and say how you would find other affected lines.
- What it tests
- How in changed between Groovy 5 and 6, the difference between a key test and a value test, and searching code for a construct whose meaning changed.
- First step
- Ask which question the line asks on each runtime. On 5.1.3, in calls the map's isCase, which tests the value's truth, so false meant off. Groovy 6.0.0 compiles in to isIn, which tests the key, and the key dryRun exists, so the check is now true.
- Follow-ups
-
- What did the old check do for options = [:]?
- How do you prove which method each runtime calls?
- Which other uses of in should the search look at?
From the lesson Operators, ranges, spread and membership
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Design a Money type for Groovy scripts so that totals read as a + b, comparisons as a < b and lists of amounts add up with sum(). Which methods do you write, what must equals and hashCode do, and what should happen when two currencies meet?
- What it tests
- Operator overloading by method names, Comparable and equality contracts, behaviour of sum() and +=, and making invalid combinations fail.
- First step
- Store the amount exactly, as a long count of the smallest unit with a currency code, then map each operator to its method: plus and minus for + and -, multiply for * by a whole number, compareTo for < and >, with equals and hashCode consistent with compareTo.
- Follow-ups
-
- What does [].sum() return, and how do you total an empty list?
- Should Money define plusAssign?
- How do you make the type work in Jenkins as well as in Groovy 6 scripts?
From the lesson Operators, ranges, spread and membership
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A helper builds deploy closures in for (service in services) { jobs[service] = { deploy(service) } } and runs them later. In a Groovy 6.0.0 script every service is deployed; on Groovy 5.1.3 and on the groovy-all 2.4.21 jar, the last service is deployed three times. Explain why and give a fix that works everywhere.
- What it tests
- How closures capture variables, the Groovy 6 change to for-in loop variables, and writing loop code whose meaning does not depend on the runtime.
- First step
- A closure keeps the variable it uses, not its value. Before Groovy 6 a for-in loop has one variable for all its passes, so every closure reads the last service when it finally runs; Groovy 6 gives each pass a fresh variable (GROOVY-11792), so the same code works there.
- Follow-ups
-
- Would a classic for loop with an index fix it?
- Why does services.each work on every runtime?
- How would a test catch this on the runtime Jenkins uses?
From the lesson Control flow, loops and the Groovy switch
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A report tool maps build results with def colour = switch (result) { case 'SUCCESS' -> 'green'; case 'FAILURE' -> 'red' }. It runs on Groovy 4.0.33 in one place and 6.0.0 in another. A new result, NOT_BUILT, appears. What does each runtime do, and how do you make the code safe for new values?
- What it tests
- Switch expressions, the Groovy 6 change for unmatched values, defensive defaults, and tests for values the code does not know yet.
- First step
- The switch's value is assigned, so it is a switch expression. On Groovy 6.0.0 an expression that no arm covers throws IllegalStateException; Groovy 4.0.33 compiled the expression into a closure that silently returned null, so the colour there is null and the problem shows up later.
- Follow-ups
-
- Is a switch statement at the end of a method affected in the same way?
- What should the default arm return?
- How would you catch new result values before they reach the tool?
From the lesson Control flow, loops and the Groovy switch
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A scripted Pipeline does def m = readFile('build.log') =~ /app:(\S+)/, then runs sh 'make deploy', and afterwards uses m[0][1]. The build fails with java.io.NotSerializableException: java.util.regex.Matcher. Explain the cause and give two fixes.
- What it tests
- How Jenkins saves a running Pipeline, which objects can be serialized, and the two ways out: extracting plain values and @NonCPS helpers, with their limits.
- First step
- Jenkins keeps a Pipeline resumable by serializing its running state, and local variables are part of that state. m holds a java.util.regex.Matcher, which is not Serializable, so saving the state while the step runs fails. Keep only the String the Matcher found.
- Follow-ups
-
- Why does the error appear only when a step runs?
- What may and may not happen inside a @NonCPS method?
- How would you test the fix without a Jenkins controller?
From the lesson Regular expressions the Groovy way
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
Write the approach for a helper that returns the highest release tag from a list such as v1.9.0, v1.10.0, v2.0.0-rc1 and release-1.2, ignoring pre-releases and names that are not versions. Which pattern do you use, how do you compare, and which cases do you test?
- What it tests
- Validating a whole value with ==~, extracting groups portably, numeric against text comparison, and choosing test cases from the traps.
- First step
- Accept only names that match the whole pattern v(\d+)\.(\d+)\.(\d+) with ==~, so v2.0.0-rc1 and release-1.2 are left out. Then compare the three numbers as integers, because as text v1.9.0 sorts after v1.10.0.
- Follow-ups
-
- Why not compare the tags as Strings?
- How do you read the groups so that the helper also runs in Jenkins?
- Which inputs belong in the tests?
From the lesson Regular expressions the Groovy way
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
All Groovy (Jenkins, Gradle and Spock) interview questions · The Groovy (Jenkins, Gradle and Spock) track